diff --git a/.gitignore b/.gitignore index c2658d7..07b1a3d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ node_modules/ +.DS_Store +extras/ diff --git a/README.md b/README.md index 74e5c30..46c8a94 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,48 @@ Tests and implementations for common data structures. See the full list in the [data-structures.md](data-structures.md) file. -Base repository for the [Core Data Structures](http://jsdev.learnersguild.org/goals/128) goal. +Project Goal: [Core Data Structures - Basic](http://jsdev.learnersguild.org/goals/156-Core_Data_Structures-Basic.html) + +Base repository: [Core Data Structures--Basic](https://github.com/diop/core-data-structures) + +Team Name: ten-seal + +Project Members: + +[Jonathan Pool](https://github.com/jrpool) + +[Fodé Diop](https://github.com/diop) ## Installation and Setup -_Fill this out_ +0. These instructions presuppose that npm (https://nodejs.org/en/) is installed. + +1. Clone this repository into a local directory. + +2. In the local directory, install required dependencies (see package.json) by executing: + + npm i + +3. In the local directory, perform the provided tests by executing: + + npm test + +4. Install ESLint (http://eslint.org) by executing: + + npm install -gS eslint + + cd + + eslint --init + +5. Edit .eslintrc.json in your home directory to customize. + +## Comments + +Each structure implementation in this repository relies on one of two features: (1) the next property of Node objects or the next and previous properties of DoubleNode objects, or (2) the properties and methods of JavaScript Array or Set objects. + +Some data structures are implemented twice in this repository. Where this is the case, they are named with “1” and “2” suffixes. A structure implementation with “1” relies on Node or DoubleNode properties; one with “2” relies on Array or Set properties and methods. -## Usage and Examples +The Node object has two implementations differing in internal validation, each with its own tests. -_...and this_ +The Set implementation is named “PowerSet” so as to avoid any confusion with JavaScript Set objects. diff --git a/data-structures.md b/data-structures.md index 7b2e43d..8ea46d0 100644 --- a/data-structures.md +++ b/data-structures.md @@ -156,87 +156,6 @@ set.clone() // returns a cloned set. _Note: if you haven't worked with sets before, you may want to read about [sets](https://www.mathsisfun.com/sets/sets-introduction.html) and [subsets](https://www.mathsisfun.com/activity/subsets.html)._ -## Advanced Data Structures - -### Hash Table ( using a LinkedList for collision chaining ) - -Maps keys to values, like a dictionary or a phone book. Or an object in JavaScript... - -From [Wikipedia](https://en.wikipedia.org/wiki/Hash_table) [edited]: - -> A data structure used to implement an associative array, a structure that can map keys to values. A hash table uses a hash function to compute an index into an array of _buckets_ or _slots_, from which the desired value can be found. - -Collision Chaining: [Wikipedia](http://www.cs.rmit.edu.au/online/blackboard/chapter/05/documents/contribute/chapter/05/chaining.html) -> Instead of storing the data directly inside the structure, have a linked list structure at each hash element. That way, all the collision, retrieval and deletion functions can be handled by the list, and the hash function's role is limited mainly to that of a guide to the algorithms, as to which hash element's list to operate on. - -```javascript -const ht = new HashTable() -ht.put("name", "Zanzibar") // adds a key-value pair to the hash table, deal with collisions using chaining -ht.get("name") // returns the data associated with key. -ht.contains("name") // returns true if the hash table contains the key. -ht.iterate((k, v) => console.log(`${k}: ${v}`)) // takes a callback function and passes it each key and value in sequence. -ht.remove("name") // removes a key-value pair by key. -ht.size() // returns the number of key-value pairs in the hash table. -HashTable.hash("name") // generates a hash for the key "name" -``` - -### Binary (Search) Tree - -A sorted binary tree for fast lookup, addition and removal of items. - -From [Wikipedia](https://en.wikipedia.org/wiki/Binary_search_tree) [edited]: - -> A particular type of container that allows fast lookup, addition and removal of items, and can be used to implement either dynamic sets of items, or lookup tables that allow finding an item by its key (e.g., finding the phone number of a person by name). -> -> Binary search trees keep their keys in sorted order, so that lookup and other operations can use the principle of binary search: when looking for a key in a tree (or a place to insert a new key), they traverse the tree from root to leaf, making comparisons to keys stored in the nodes of the tree and deciding, based on the comparison, to continue searching in the left or right subtrees. - -```javascript -const bst = new BinarySearchTree() -bst.insert(3) // inserts a node with the specified value into the tree. -bst.search(3) // returns a node object or null if not found. -bst.remove(3) // removes an value's node (if exists) from the tree. -bst.traverse((val) => console.log(val)) // traverse the tree using in-order traversal and apply function on each node's value. -bst.count() // return the number of nodes in the tree. -``` - -#### Tree Node - -To implement a _standard_ binary search tree, use a **tree node** data structure in your implementation. Use this interface as a reference: - -```javascript -const leastNode = new TreeNode({data: 3}) -const moreNode = new TreeNode({data: 10}) -const midNode = new TreeNode({data: 7, left: leastNode, right: moreNode}) - -midNode.getData() // returns the node's data -midNode.getLeft() // returns the left node or null if none -midNode.setLeft(leastNode) // changes the reference to the left node and returns the original node -midNode.getRight() // returns the right node or null if none -midNode.setRight(moreNode) // changes the reference to the right node and returns the original node -``` - -### Directed Graph - -Nodes connected by vertices with a direction. - -From [Wikipedia](https://en.wikipedia.org/wiki/Directed_graph) [edited]: - -> A graph (that is a set of vertices connected by edges), where the edges have a direction associated with them. - -```javascript -const diGraph = new DirectedGraph() -diGraph.addVertex('v1') // adds a vertex to the graph. -diGraph.hasVertex('v1') // returns true if the graph contains the vertex or false if not. -diGraph.addDirection('v1', 'v2') // adds a direction from 'v1' to 'v2'. -diGraph.hasDirection('v1', 'v2') // returns true if there's a direction from 'v1' to 'v2'. -diGraph.visit('v1', vertex => console.log(vertex)) // visit all the connected vertices in the graph starting with v1 and apply function on the reached vertex. -diGraph.findPaths('v1', 'v2') // returns an array of all the paths between two vertices. -diGraph.removeDirection('v1', 'v2') // removes an existing direction between 'v1' and 'v2'. -diGraph.getSeparatedVertices() // returns an array of all the vertices that are unconnected to the graph (have no direction linking them to another vertex). -diGraph.removeVertex('v1') // removes an existing vertex and all its directions (the incoming and outgoing). -diGraph.count() // returns the number of vertices in the graph. -``` - ### Sources Most of the above was shamelessly borrowed from Wikipedia and these libraries: diff --git a/package.json b/package.json index d47a351..158a12c 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,24 @@ { "name": "core-data-structures", - "description": "Tests and implementations for common data structures.", + "description": "Tests and implementations for basic core data structures.", "private": false, "version": "0.0.0", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/diop/core-data-structures" + }, + "dependencies": { + "babel-preset-es2015": "^6.24.1", + "babel-register": "^6.24.1" + }, "devDependencies": { "babel-cli": "^6.18.0", "babel-preset-env": "^1.1.4", "babel-register": "^6.18.0", - "chai": "~1.8.0", + "chai": "^1.8.1", "chai-change": "^2.1.2", - "mocha": "2.0.1" + "mocha": "^2.0.1" }, "scripts": { "build": "babel src -d lib", diff --git a/spec/double_node.js b/spec/double_node.js new file mode 100644 index 0000000..e8576dc --- /dev/null +++ b/spec/double_node.js @@ -0,0 +1,211 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import DoubleNode from '../src/double_node' + +chai.use(chaiChange) + +describe('DoubleNode', function() { + 'use strict' + + it('is a function', function() { + expect(DoubleNode).to.be.a('function') + }) + + context('getData()', function() { + const dsNode = new DoubleNode({data: 'string'}) + const dnNode = new DoubleNode({data: 0}) + const daNode = new DoubleNode({data: [1, 2]}) + const doNode = new DoubleNode({data: {'a': 1, 'b': 2}}) + const emptyNode = new DoubleNode() + const ngNode = new DoubleNode({data: 1, next: dsNode}) + const nbNode = new DoubleNode({data: 2, next: "dnNode"}) + const pgNode = new DoubleNode({data: 3, previous: dnNode}) + const pgbNode = new DoubleNode({data: 4, previous: emptyNode}) + it('returns the correct type, if a string', function() { + expect(dsNode.getData()).to.be.a('string') + }) + it('returns the correct value, if a string', function() { + expect(dsNode.getData()).to.be.equal('string') + }) + it('returns the correct type, if a number', function() { + expect(dnNode.getData()).to.be.a('number') + }) + it('returns the correct value, if a number', function() { + expect(dnNode.getData()).to.be.equal(0) + }) + it('returns the correct type, if an array', function() { + expect(Array.isArray(daNode.getData())).to.be.true + }) + it('returns the correct value, if an array', function() { + expect(daNode.getData()).to.be.deep.equal([1, 2]) + }) + it('returns the correct type, if an object', function() { + expect(doNode.getData()).to.be.an('object') + }) + it('returns the correct value, if an object', function() { + expect(doNode.getData()).to.be.deep.equal({'a': 1, 'b': 2}) + }) + it('returns an object with a retrievable property, if an object', function() { + expect(doNode.getData().a).to.be.equal(1) + }) + it('returns the correct value, if next is specified', function() { + expect(ngNode.getData()).to.be.equal(1) + }) + it('returns the correct value, if previous is specified', function() { + expect(pgNode.getData()).to.be.equal(3) + }) + it('returns the correct value, if previous is a bad DoubleNode', function() { + expect(pgbNode.getData()).to.be.equal(4) + }) + it('returns undefined, if unspecified', function() { + expect(emptyNode.getData()).to.be.undefined + }) + it('returns undefined, if next is not a DoubleNode', function() { + expect(nbNode.getData()).to.be.undefined + }) + }) + + context('getNext()', function() { + const nnNode = new DoubleNode({data: 'string'}) + const ngNode = new DoubleNode({data: 0, next: nnNode}) + const nbNode = new DoubleNode({data: 1, next: 0}) + const dxnbNode = new DoubleNode({next: ngNode}) + const ngbNode = new DoubleNode({data: 0, next: dxnbNode}) + const pgNode = new DoubleNode({data: 2, next: nnNode, previous: nnNode}) + const pgbNode = new DoubleNode({data: 3, next: ngNode, previous: nbNode}) + const pbNode = new DoubleNode({data: 4, next: ngNode, previous: "node"}) + it('returns the correct type, if specified', function() { + expect(ngNode.getNext() instanceof DoubleNode).to.be.true + }) + it('returns the correct value, if specified', function() { + expect(ngNode.getNext()).to.be.deep.equal(nnNode) + }) + it( + 'returns a DoubleNode with a retrievable property, if specified', + function() { + expect(ngNode.getNext().getData()).to.be.equal('string') + } + ) + it('returns null, if unspecified', function() { + expect(nnNode.getNext()).to.be.null + }) + it('returns null, if specified but not a DoubleNode', function() { + expect(nbNode.getNext()).to.be.null + }) + it('returns null, if specified but data is unspecified', function() { + expect(dxnbNode.getNext()).to.be.null + }) + }) + + context('setNext()', function() { + const nnNode = new DoubleNode({data: 0}) + const ngNode = new DoubleNode({data: 'string', next: nnNode}) + const nbNode = new DoubleNode({data: 1, next: "nonode"}) + const pbNode = new DoubleNode({data: 2, next: ngNode, previous: "bad"}) + it('returns a DoubleNode', function() { + expect(nnNode.setNext(ngNode) instanceof DoubleNode).to.be.true + }) + it('returns the correct DoubleNode', function() { + expect(nnNode.setNext(ngNode)).to.be.deep.equal(nnNode) + }) + it( + 'returns a DoubleNode with a retrievable data property', + function() { + expect(ngNode.setNext(nnNode).getData()).to.be.equal('string') + } + ) + it( + 'returns a DoubleNode with a retrievable good new next property', + function() { + expect(nnNode.setNext(ngNode).getNext()).to.be.deep.equal(ngNode) + } + ) + it( + 'returns a DoubleNode with a retrievable bad new next property', + function() { + expect(nnNode.setNext(nbNode).getNext()).to.be.deep.equal(nbNode) + } + ) + it( + 'fails to change next (from last value), if newNext is invalid', + function() { + expect(nnNode.setNext("badNode").getNext()).to.be.deep.equal(nbNode) + } + ) + }) + + context('getPrevious()', function() { + const nnNode = new DoubleNode({data: 'astring'}) + const ngNode = new DoubleNode({data: 0, next: nnNode}) + const nbNode = new DoubleNode({data: 1, next: 0}) + const dxpbNode = new DoubleNode({previous: ngNode}) + const pgNode = new DoubleNode({data: 2, next: nnNode, previous: nnNode}) + const pgbNode = new DoubleNode({data: 3, next: ngNode, previous: nbNode}) + const pbNode = new DoubleNode({data: 4, next: ngNode, previous: "node"}) + it('returns the correct type, if specified', function() { + expect(pgNode.getPrevious() instanceof DoubleNode).to.be.true + }) + it('returns a DoubleNode, if an invalid one is specified', function() { + expect(pgbNode.getPrevious() instanceof DoubleNode).to.be.true + }) + it('returns the correct value, if specified', function() { + expect(pgNode.getPrevious()).to.be.deep.equal(nnNode) + }) + it( + 'returns a DoubleNode with a retrievable property, if specified', + function() { + expect(pgNode.getPrevious().getData()).to.be.equal('astring') + } + ) + it('returns null, if unspecified', function() { + expect(nnNode.getPrevious()).to.be.null + }) + it('returns null, if specified but not a DoubleNode', function() { + expect(pbNode.getPrevious()).to.be.null + }) + it('returns null, if specified but data is unspecified', function() { + expect(dxpbNode.getPrevious()).to.be.null + }) + }) + + context('setPrevious()', function() { + const nnNode = new DoubleNode({data: 0}) + const ngNode = new DoubleNode({data: 'astring', next: nnNode}) + const nbNode = new DoubleNode({data: 1, next: "nonode"}) + const pbNode = new DoubleNode({data: 2, next: ngNode, previous: "bad"}) + it('returns a DoubleNode', function() { + expect(nnNode.setPrevious(ngNode) instanceof DoubleNode).to.be.true + }) + it('returns the correct DoubleNode', function() { + expect(nnNode.setPrevious(ngNode)).to.be.deep.equal(nnNode) + }) + it( + 'returns a DoubleNode with a retrievable data property', + function() { + expect(ngNode.setPrevious(nnNode).getData()).to.be.equal('astring') + } + ) + it( + 'returns a DoubleNode with a retrievable good new previous property', + function() { + expect(nnNode.setPrevious(nnNode).getPrevious()) + .to.be.deep.equal(nnNode) + } + ) + it( + 'returns a DoubleNode with a retrievable bad new next property', + function() { + expect(nnNode.setPrevious(nbNode).getPrevious()) + .to.be.deep.equal(nbNode) + } + ) + it( + 'fails to change previous (from last value), if newPrevious is invalid', + function() { + expect(nnNode.setPrevious("badNode").getPrevious()) + .to.be.deep.equal(nbNode) + } + ) + }) + +}) diff --git a/spec/doubly_linked_list.js b/spec/doubly_linked_list.js new file mode 100644 index 0000000..9366f6e --- /dev/null +++ b/spec/doubly_linked_list.js @@ -0,0 +1,118 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import DoublyLinkedList from '../src/doubly_linked_list' + +chai.use(chaiChange) + +describe('DoublyLinkedList', function() { + 'use strict' + + it('is a function', function() { + expect(DoublyLinkedList).to.be.a('function') + }) + + context('empty list interrogation', function() { + const emptyList = new DoublyLinkedList() + it('getHeadNode() returns null', function() { + expect(emptyList.getHeadNode()).to.be.null + }) + it('getTailNode() returns null', function() { + expect(emptyList.getTailNode()).to.be.null + }) + it('isEmpty() returns true', function() { + expect(emptyList.isEmpty()).to.be.true + }) + it('size() returns 0', function() { + expect(emptyList.size()).to.be.equal(0) + }) + it('contains("x") returns false', function() { + expect(emptyList.contains('x')).to.be.false + }) + it('find("x") returns -1', function() { + expect(emptyList.find('x')).to.be.equal(-1) + }) + }) + + context('non-empty list interrogation', function() { + const dlList = new DoublyLinkedList() + dlList.insert('bad') + dlList.insert('ok') + dlList.insert('good') + it('the element getHeadNode() returns has data “bad”', function() { + expect(dlList.getHeadNode().getData()).to.be.equal('bad') + }) + it('the element getTailNode() returns has data “good”', function() { + expect(dlList.getTailNode().getData()).to.be.equal('good') + }) + it('contains("ok") returns true', function() { + expect(dlList.contains('ok')).to.be.true + }) + it('contains("mediocre") returns false', function() { + expect(dlList.contains('mediocre')).to.be.false + }) + it('find("ok") returns a node with data “ok”', function() { + expect(dlList.find('ok').getData()).to.be.equal('ok') + }) + it('find("mediocre") returns -1', function() { + expect(dlList.find('mediocre')).to.be.equal(-1) + }) + it('isEmpty() returns false', function() { + expect(dlList.isEmpty()).to.be.false + }) + it('size() returns 3', function() { + expect(dlList.size()).to.be.equal(3) + }) + }) + + context('list manipulation', function() { + const dlList = new DoublyLinkedList() + dlList.insert('bad') + dlList.insert('ok') + dlList.insert('good') + dlList.insertFirst('horrible') + it(('insertFirst() changes the head (first) element'), function() { + expect(dlList.getHeadNode().getData()).to.be.equal('horrible') + }) + it('insertFirst() does not change the tail (last) element', function() { + expect(dlList.getTailNode().getData()).to.be.equal('good') + }) + it('no element has data “mediocre”', function() { + expect(dlList.contains('mediocre')).to.be.false + }) + it('insertBefore() inserts an element where specified', function() { + dlList.insertBefore('ok', 'mediocre') + expect(dlList.contains('mediocre')).to.be.true + }) + it('insertBefore() inserts nothing if no target exists', function() { + const dlSizeOld = dlList.size(); + dlList.insertBefore('stupendous', 'superb') + expect(dlList.size()).to.be.equal(dlSizeOld) + }) + it('insertAfter() not after the last element does not change it', function() { + dlList.insertAfter('horrible', 'lousy') + expect(dlList.getTailNode().getData()).to.be.equal('good') + }) + it('insertAfter() after the last element changes it', function() { + dlList.insertAfter('good', 'excellent') + expect(dlList.getTailNode().getData()).to.be.equal('excellent') + }) + it('insertAfter() inserts nothing if no target exists', function() { + const dlSizeOld = dlList.size(); + dlList.insertAfter('superb', 'stupendous') + expect(dlList.size()).to.be.equal(dlSizeOld) + }) + it('remove() changes the tail (last) element', function() { + dlList.remove() + expect(dlList.getTailNode().getData()).to.be.equal('good') + }) + it('removeFirst() changes the head (first) element', function() { + dlList.removeFirst() + expect(dlList.getHeadNode().getData()).to.be.equal('lousy') + }) + it('clear() makes the list’s size 0', function() { + dlList.clear() + expect(dlList.size()).to.be.equal(0) + }) + }) + +}) diff --git a/spec/linked_list1.js b/spec/linked_list1.js new file mode 100644 index 0000000..59ac927 --- /dev/null +++ b/spec/linked_list1.js @@ -0,0 +1,107 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import Node from '../src/node1' +import LinkedList from '../src/linked_list1' + +chai.use(chaiChange) + +describe('LinkedList1', () => { + 'use strict' + const list = new LinkedList() + list.insert( {data: 'apple'} ) + list.insert( {data: 'banana'} ) + list.insert( {data: 'orange'} ) + + it('is a function', () => { + expect( LinkedList ).to.be.a( 'function' ) + }) + + context( 'getHeadNode', () => { + it( 'returns the first node in the list', () => { + expect ( list.getHeadNode().data ).to.equal( 'apple' ) + }) + }) + + context( 'getTailNode', () => { + it( 'returns the lastnode in the list', () => { + expect( list.getTailNode().data ).to.equal( 'orange') + }) + }) + + context( 'insert()', () => { + it( 'inserts node to the tail', () => { + list.insert( { data: 'papaya' } ) + expect( list.tail.data ).to.equal( 'papaya') + }) + }) + + context( 'contains()', () => { + it( 'determines whether or not the list contains the provided data', () => { + expect( list.contains( 'cherry' ) ).to.equal( false ) + }) + }) + + context( 'find()', () => { + it( 'returns the first node contains the prodived data', () => { + expect( list.find( 'banana' ).getData() ).to.equal( 'banana' ) + }) + it( 'returns -1 if not found', () => { + expect( list.find( 'cherry' ) ).to.equal(-1) + }) + }) + + context( 'insertFirst()', () => { + it( 'inserts a node at the head of the list', () => { + list.insertFirst( { data: 'bananas' } ) + expect( list.head.data ).to.equal( 'bananas') + }) + }) + + context( 'insertBefore', () => { + it( 'inserts a node before a specified node', () => { + list.insertBefore( 'bananas', 'apples' ) + expect( list.head.next.data ).to.equal( 'bananas' ) + }) + }) + + context( 'insertfAter', () => { + it( 'inserts a node after a specified node', () => { + list.insertAfter( 'apples', 'guava' ) + expect( list.head.next.next.next.data ).to.equal( 'banana') + }) + }) + + context( 'remove()', () => { + it( 'removes the tail node from the list', () => { + list.remove() + expect( list.length ).to.equal(5) + }) + }) + + context( 'removeFirst()', () => { + it( 'removes the head node from the list' , () => { + list.removeFirst() + expect( list.head.data ).to.equal( 'bananas' ) + }) + }) + + context( 'isEmpty()', () => { + it( ' determines whether the list is empty', () => { + expect( list.isEmpty() ).to.equal(false) + }) + }) + + context( 'size()', () => { + it( 'returns the size of the list', () => { + expect( list.size() ).to.equal(5) + }) + }) + + context( 'clear()', () => { + it( ' clears the list of all nodes and data', () => { + list.clear() + expect( list.length ).to.equal(0) + }) + }) + +}) diff --git a/spec/linked_list2.js b/spec/linked_list2.js new file mode 100644 index 0000000..e9810a0 --- /dev/null +++ b/spec/linked_list2.js @@ -0,0 +1,118 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import LinkedList from '../src/linked_list2' + +chai.use(chaiChange) + +describe('LinkedList2', function() { + 'use strict' + + it('is a function', function() { + expect(LinkedList).to.be.a('function') + }) + + context('empty list interrogation', function() { + const emptyList = new LinkedList() + it('getHeadNode() returns null', function() { + expect(emptyList.getHeadNode()).to.be.null + }) + it('getTailNode() returns null', function() { + expect(emptyList.getTailNode()).to.be.null + }) + it('isEmpty() returns true', function() { + expect(emptyList.isEmpty()).to.be.true + }) + it('size() returns 0', function() { + expect(emptyList.size()).to.be.equal(0) + }) + it('contains("x") returns false', function() { + expect(emptyList.contains('x')).to.be.false + }) + it('find("x") returns -1', function() { + expect(emptyList.find('x')).to.be.equal(-1) + }) + }) + + context('non-empty list interrogation', function() { + const lList = new LinkedList() + lList.insert('bad') + lList.insert('ok') + lList.insert('good') + it('the element getHeadNode() returns has data “bad”', function() { + expect(lList.getHeadNode().getData()).to.be.equal('bad') + }) + it('the element getTailNode() returns has data “good”', function() { + expect(lList.getTailNode().getData()).to.be.equal('good') + }) + it('contains("ok") returns true', function() { + expect(lList.contains('ok')).to.be.true + }) + it('contains("mediocre") returns false', function() { + expect(lList.contains('mediocre')).to.be.false + }) + it('find("ok") returns a node with data “ok”', function() { + expect(lList.find('ok').getData()).to.be.equal('ok') + }) + it('find("mediocre") returns -1', function() { + expect(lList.find('mediocre')).to.be.equal(-1) + }) + it('isEmpty() returns false', function() { + expect(lList.isEmpty()).to.be.false + }) + it('size() returns 3', function() { + expect(lList.size()).to.be.equal(3) + }) + }) + + context('list manipulation', function() { + const lList = new LinkedList() + lList.insert('bad') + lList.insert('ok') + lList.insert('good') + lList.insertFirst('horrible') + it(('insertFirst() changes the head (first) element'), function() { + expect(lList.getHeadNode().getData()).to.be.equal('horrible') + }) + it('insertFirst() does not change the tail (last) element', function() { + expect(lList.getTailNode().getData()).to.be.equal('good') + }) + it('no element has data “mediocre”', function() { + expect(lList.contains('mediocre')).to.be.false + }) + it('insertBefore() inserts an element where specified', function() { + lList.insertBefore('ok', 'mediocre') + expect(lList.contains('mediocre')).to.be.true + }) + it('insertBefore() inserts nothing if no target exists', function() { + const lSizeOld = lList.size(); + lList.insertBefore('stupendous', 'superb') + expect(lList.size()).to.be.equal(lSizeOld) + }) + it('insertAfter() not after the last element does not change it', function() { + lList.insertAfter('horrible', 'lousy') + expect(lList.getTailNode().getData()).to.be.equal('good') + }) + it('insertAfter() after the last element changes it', function() { + lList.insertAfter('good', 'excellent') + expect(lList.getTailNode().getData()).to.be.equal('excellent') + }) + it('insertAfter() inserts nothing if no target exists', function() { + const lSizeOld = lList.size(); + lList.insertAfter('superb', 'stupendous') + expect(lList.size()).to.be.equal(lSizeOld) + }) + it('remove() changes the tail (last) element', function() { + lList.remove() + expect(lList.getTailNode().getData()).to.be.equal('good') + }) + it('removeFirst() changes the head (first) element', function() { + lList.removeFirst() + expect(lList.getHeadNode().getData()).to.be.equal('lousy') + }) + it('clear() makes the list’s size 0', function() { + lList.clear() + expect(lList.size()).to.be.equal(0) + }) + }) + +}) diff --git a/spec/node1.js b/spec/node1.js new file mode 100644 index 0000000..8c839ef --- /dev/null +++ b/spec/node1.js @@ -0,0 +1,46 @@ + +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import Node from '../src/node1' + +chai.use(chaiChange) + +describe('Node1', () => { + 'use strict' + + it('is a function', () => { + expect(Node).to.be.a('function') + }) + + context('getData()', () => { + const nodeA = new Node( {data: 'apple'}) + it('gets the data from the node.', () => { + expect( nodeA.getData() ).to.equal('apple') + }) + }) + + context('setNext()', () => { + const nodeA = new Node( {data: 'apple'} ) + const nodeB = new Node( {data: 'banana'} ) + const nodeC = new Node( {data: 'orange'} ) + it('points the reference to the next node.', () => { + expect( () => nodeA.setNext(nodeB) ).to.alter( () => nodeA.next, { from: null, to: nodeB } ) + }) + it('returns the original.', () => { + expect( nodeA.setNext(nodeB) ).to.equal(nodeA) + }) + }) + + context('getNext()', () => { + const nodeA = new Node( {data: 'apple'} ) + const nodeB = new Node( {data: 'banana'} ) + const nodeC = new Node( {data: 'orange'} ) + it('returns the next node', () => { + nodeA.setNext(nodeB) + expect( nodeA.getNext() ).to.equal(nodeB) + }) + it('returns null if no next node.', () => { + expect( nodeB.getNext() ).to.equal(null) + }) + }) +}) diff --git a/spec/node2.js b/spec/node2.js new file mode 100644 index 0000000..3497d57 --- /dev/null +++ b/spec/node2.js @@ -0,0 +1,133 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import Node from '../src/node2' + +chai.use(chaiChange) + +describe('Node2', function() { + 'use strict' + + it('is a function', function() { + expect(Node).to.be.a('function') + }) + + context('getData()', function() { + const dsNode = new Node({data: 'string'}) + const dnNode = new Node({data: 0}) + const daNode = new Node({data: [1, 2]}) + const doNode = new Node({data: {'a': 1, 'b': 2}}) + const emptyNode = new Node() + const ngNode = new Node({data: 1, next: dsNode}) + const nbNode = new Node({data: 2, next: "dnNode"}) + const ngbNode = new Node({data: 3, next: nbNode}) + it('returns the correct type, if a string', function() { + expect(dsNode.getData()).to.be.a('string') + }) + it('returns the correct value, if a string', function() { + expect(dsNode.getData()).to.be.equal('string') + }) + it('returns the correct type, if a number', function() { + expect(dnNode.getData()).to.be.a('number') + }) + it('returns the correct value, if a number', function() { + expect(dnNode.getData()).to.be.equal(0) + }) + it('returns the correct type, if an array', function() { + expect(Array.isArray(daNode.getData())).to.be.true + }) + it('returns the correct value, if an array', function() { + expect(daNode.getData()).to.be.deep.equal([1, 2]) + }) + it('returns the correct type, if an object', function() { + expect(doNode.getData()).to.be.an('object') + }) + it('returns the correct value, if an object', function() { + expect(doNode.getData()).to.be.deep.equal({'a': 1, 'b': 2}) + }) + it('returns an object with a retrievable property, if an object', function() { + expect(doNode.getData().a).to.be.equal(1) + }) + it('returns the correct value, if next is specified', function() { + expect(ngNode.getData()).to.be.equal(1) + }) + it('returns the correct value, if next is a bad Node', function() { + expect(ngbNode.getData()).to.be.equal(3) + }) + it('returns undefined, if unspecified', function() { + expect(emptyNode.getData()).to.be.undefined + }) + it('returns undefined, if next is not a Node', function() { + expect(nbNode.getData()).to.be.undefined + }) + }) + + context('getNext()', function() { + const nnNode = new Node({data: 'string'}) + const ngNode = new Node({data: 0, next: nnNode}) + const nbNode = new Node({data: 1, next: 0}) + const dxnbNode = new Node({next: ngNode}) + const ngbNode = new Node({data: 0, next: dxnbNode}) + const emptyNode = new Node() + it('returns the correct type, if specified', function() { + expect(ngNode.getNext() instanceof Node).to.be.true + }) + it('returns the correct value, if specified', function() { + expect(ngNode.getNext()).to.be.deep.equal(nnNode) + }) + it( + 'returns a Node with a retrievable property, if specified', + function() { + expect(ngNode.getNext().getData()).to.be.equal('string') + } + ) + it('returns null, if unspecified', function() { + expect(nnNode.getNext()).to.be.null + }) + it('returns null, if specified but not a Node', function() { + expect(nbNode.getNext()).to.be.null + }) + it('returns null, if specified but data is unspecified', function() { + expect(dxnbNode.getNext()).to.be.null + }) + it('returns null, if data unspecified', function() { + expect(emptyNode.getNext()).to.be.null + }) + }) + + context('setNext()', function() { + const nnNode = new Node({data: 0}) + const ngNode = new Node({data: 'astring', next: nnNode}) + const nbNode = new Node({data: 1, next: "nonode"}) + it('returns a Node', function() { + expect(nnNode.setNext(ngNode) instanceof Node).to.be.true + }) + it('returns the correct Node', function() { + expect(nnNode.setNext(ngNode)).to.be.deep.equal(nnNode) + }) + it( + 'returns a Node with a retrievable data property', + function() { + expect(ngNode.setNext(nnNode).getData()).to.be.equal('astring') + } + ) + it( + 'returns a Node with a retrievable good new next property', + function() { + expect(nnNode.setNext(ngNode).getNext()).to.be.deep.equal(ngNode) + } + ) + it( + 'returns a Node with a retrievable bad new next property', + function() { + expect(nnNode.setNext(nbNode).getNext()).to.be.deep.equal(nbNode) + } + ) + it( + 'fails to change next (from last value), if newNext is invalid', + function() { + expect(nnNode.setNext("badNode").getNext()).to.be.deep.equal(nbNode) + } + ) + }) + +}) diff --git a/spec/power_set.js b/spec/power_set.js new file mode 100644 index 0000000..8ce0118 --- /dev/null +++ b/spec/power_set.js @@ -0,0 +1,144 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import PowerSet from '../src/power_set' + +chai.use(chaiChange) + +describe('PowerSet', function() { + 'use strict' + + it('is a function', function() { + expect(PowerSet).to.be.a('function') + }) + + context('empty powerSet interrogation', function() { + const emptyPowerSet = new PowerSet([]) + it('isEmpty() returns true', function() { + expect(emptyPowerSet.isEmpty()).to.be.true + }) + it('contains("x") returns false', function() { + expect(emptyPowerSet.contains('x')).to.be.false + }) + it('size() returns 0', function() { + expect(emptyPowerSet.size()).to.equal(0) + }) + }) + + context('non-empty powerSet interrogation', function() { + const powerSet = new PowerSet([1, 2, 4, 8, 16]) + it('isEmpty() returns false', function() { + expect(powerSet.isEmpty()).to.be.false + }) + it('contains(4) returns true', function() { + expect(powerSet.contains(4)).to.be.true + }) + it('contains(5) returns false', function() { + expect(powerSet.contains(5)).to.be.false + }) + it('size() returns 5', function() { + expect(powerSet.size()).to.equal(5) + }) + }) + + context('dual-set interrogation', function() { + const ps0 = new PowerSet([3, 6, 9, 12, 15]) + const ps1 = new PowerSet([3, 6, 12, 24]) + it(('ps0.union(ps1) returns a powerSet of 6 elements'), function() { + expect(ps0.union(ps1).size()).to.equal(6) + }) + it(('ps0.union(ps1) returns a powerSet containing 15'), function() { + expect(ps0.union(ps1).contains(15)).to.be.true + }) + it(('ps0.union(ps0) returns a powerSet containing 15'), function() { + expect(ps1.union(ps0).contains(15)).to.be.true + }) + it(('ps0.intersect(ps1) returns a powerSet of 3 elements'), function() { + expect(ps0.intersect(ps1).size()).to.equal(3) + }) + it(('ps0.intersect(ps1) returns a powerSet containing 6'), function() { + expect(ps0.intersect(ps1).contains(6)).to.be.true + }) + it(('ps0.intersect(ps1) returns a powerSet not containing 9'), function() { + expect(ps0.intersect(ps1).contains(9)).to.be.false + }) + it(('ps1.intersect(ps0) returns a powerSet of 3 elements'), function() { + expect(ps1.intersect(ps0).size()).to.equal(3) + }) + it(('ps1.intersect(ps0) returns a powerSet containing 6'), function() { + expect(ps1.intersect(ps0).contains(6)).to.be.true + }) + it(('ps1.intersect(ps0) returns a powerSet not containing 9'), function() { + expect(ps1.intersect(ps0).contains(9)).to.be.false + }) + it(('ps0.difference(ps1) returns a powerSet of 2 elements'), function() { + expect(ps0.difference(ps1).size()).to.equal(2) + }) + it(('ps0.difference(ps1) returns a powerSet containing 9'), function() { + expect(ps0.difference(ps1).contains(9)).to.be.true + }) + it(('ps0.difference(ps1) returns a powerSet not containing 6'), function() { + expect(ps0.difference(ps1).contains(6)).to.be.false + }) + it(('ps1.difference(ps0) returns a powerSet of 1 element'), function() { + expect(ps1.difference(ps0).size()).to.equal(1) + }) + it(('ps1.difference(ps0) returns a powerSet containing 24'), function() { + expect(ps1.difference(ps0).contains(24)).to.be.true + }) + it(('ps1.difference(ps0) returns a powerSet not containing 6'), function() { + expect(ps1.difference(ps0).contains(6)).to.be.false + }) + }) + + context('set manipulation', function() { + const powerSet = new PowerSet([1, 2, 4, 8, 16]) + it('add() increases the powerSet size', function() { + const oldSize = powerSet.size() + powerSet.add(32) + expect(powerSet.size()).to.equal(oldSize + 1) + }) + it('adding an existing element makes no change', function() { + const oldSize = powerSet.size() + powerSet.add(4) + expect(powerSet.size()).to.equal(oldSize) + }) + it('add() makes the powerSet contain the added element', function() { + expect(powerSet.contains(64)).to.be.false + powerSet.add(64) + expect(powerSet.contains(64)).to.be.true + }) + it('remove() decreases the powerSet size', function() { + const oldSize = powerSet.size() + powerSet.remove(64) + expect(powerSet.size()).to.equal(oldSize - 1) + }) + it('removing a nonexistent element makes no change', function() { + const oldSize = powerSet.size() + powerSet.remove(33) + expect(powerSet.size()).to.equal(oldSize) + }) + it('remove() makes the powerSet omit the removed element', function() { + expect(powerSet.contains(32)).to.be.true + powerSet.remove(32) + expect(powerSet.contains(32)).to.be.false + }) + it('clone() returns a distinct identical powerSet', function() { + const newPowerSet = powerSet.clone() + expect(newPowerSet).to.not.equal(powerSet) + expect(newPowerSet).to.deep.equal(powerSet) + }) + }) + + context('element iteration', function() { + const powerSet = new PowerSet([1, 2, 4, 8, 16]) + const newPowerSet = new PowerSet([]) + it('forEach() can add 1 to each element', function() { + const plusOne = function(value, key, thisArg) { + newPowerSet.add(value + 1); + } + powerSet.forEach(element => plusOne(element)) + expect(newPowerSet.contains(8)).to.be.false + expect(newPowerSet.contains(9)).to.be.true + }) + }) +}) diff --git a/spec/priority_node.js b/spec/priority_node.js new file mode 100644 index 0000000..8d9cfd2 --- /dev/null +++ b/spec/priority_node.js @@ -0,0 +1,191 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import PriorityNode from '../src/priority_node' + +chai.use(chaiChange) + +describe('PriorityNode', function() { + 'use strict' + + it('is a function', function() { + expect(PriorityNode).to.be.a('function') + }) + + context('getData()', function() { + const levelZero = new PriorityNode({data: 'level0', priority: 0}) + const level0 = new PriorityNode({data: 0, priority: 0}) + const levelArrayNum = new PriorityNode({data: [1, 2], priority: 0}) + const levelArrayString = new PriorityNode({data: ['a', 'b'], priority: 0}) + const levelObj = new PriorityNode({data: {'a': 1, 'b': 2}, priority: 0}) + const priUnd = new PriorityNode({data: 'level0'}) + const levelUnd = new PriorityNode({priority: 0}) + const levZPriZ = new PriorityNode({data: 'level0', priority: 'zero'}) + const next00 = new PriorityNode({data: -1, priority: 0, next: level0}) + const next0Num = new PriorityNode({data: -1, priority: 0, next: 0}) + it('returns the correct type, if a string', function() { + expect(levelZero.getData()).to.be.a('string') + }) + it('returns the correct value, if a string', function() { + expect(levelZero.getData()).to.be.equal('level0') + }) + it('returns the correct type, if a number', function() { + expect(level0.getData()).to.be.a('number') + }) + it('returns the correct value, if a number', function() { + expect(level0.getData()).to.be.equal(0) + }) + it('returns the correct type, if an array', function() { + expect(Array.isArray(levelArrayNum.getData())).to.be.true + }) + it('returns the correct value, if an array', function() { + expect(levelArrayString.getData()).to.be.deep.equal(['a', 'b']) + }) + it('returns the correct type, if an object', function() { + expect(levelObj.getData()).to.be.an('object') + }) + it('returns the correct value, if an object', function() { + expect(levelObj.getData()).to.be.deep.equal({'a': 1, 'b': 2}) + }) + it('returns an object with a retrievable property, if an object', function() { + expect(levelObj.getData().a).to.be.equal(1) + }) + it('returns the correct value, if priority is unspecified', function() { + expect(priUnd.getData()).to.be.equal('level0') + }) + it('returns the correct value, if priority is unspecified', function() { + expect(priUnd.getData()).to.be.equal('level0') + }) + it('returns the correct value, if next node is specified', function() { + expect(next00.getData()).to.be.equal(-1) + }) + it('returns undefined, if unspecified', function() { + expect(levelUnd.getData()).to.be.undefined + }) + it('returns undefined, if priority is not a number', function() { + expect(levZPriZ.getData()).to.be.undefined + }) + it('returns undefined, if next is not a PriorityNode', function() { + expect(next0Num.getData()).to.be.undefined + }) + }) + + context('getPriority()', function() { + const level0 = new PriorityNode({data: 0, priority: 0}) + const next00 = new PriorityNode({data: -1, priority: 0, next: level0}) + const priUnd = new PriorityNode({data: 'level0'}) + const levelUnd = new PriorityNode({priority: 0}) + const levZPriZ = new PriorityNode({data: 'level0', priority: 'zero'}) + const next0Num = new PriorityNode({data: -1, priority: 0, next: 0}) + it('returns the correct type', function() { + expect(level0.getPriority()).to.be.a('number') + }) + it('returns the correct value', function() { + expect(level0.getPriority()).to.be.equal(0) + }) + it('returns the correct value, if next node is specified', function() { + expect(next00.getPriority()).to.be.equal(0) + }) + it('returns the default value, if unspecified', function() { + expect(priUnd.getPriority()).to.be.equal(0) + }) + it('returns undefined, if data unspecified', function() { + expect(levelUnd.getPriority()).to.be.undefined + }) + it('returns undefined, if not a number', function() { + expect(levZPriZ.getPriority()).to.be.undefined + }) + it('returns undefined, if next is not a PriorityNode', function() { + expect(next0Num.getPriority()).to.be.undefined + }) + }) + + context('getNext()', function() { + const level0 = new PriorityNode({data: 0, priority: 0}) + const next00 = new PriorityNode({data: -1, priority: 0, next: level0}) + const next0Num = new PriorityNode({data: -1, priority: 0, next: 0}) + const levelUnd = new PriorityNode({priority: 0}) + const levZPriZ = new PriorityNode({data: 'level0', priority: 'zero'}) + it('returns the correct type, if specified', function() { + expect(next00.getNext() instanceof PriorityNode).to.be.true + }) + it('returns the correct value, if specified', function() { + expect(next00.getNext()).to.be.deep.equal(level0) + }) + it( + 'returns a PriorityNode with a retrievable property, if specified', + function() { + expect(next00.getNext().getData()).to.be.equal(0) + } + ) + it('returns null, if unspecified', function() { + expect(level0.getNext()).to.be.null + }) + it('returns null, if specified but not a PriorityNode', function() { + expect(next0Num.getNext()).to.be.null + }) + it('returns null, if data is unspecified', function() { + expect(levelUnd.getNext()).to.be.null + }) + it('returns null, if priority is not a number', function() { + expect(levZPriZ.getNext()).to.be.null + }) + }) + + context('setPriority()', function() { + const level0 = new PriorityNode({data: 0, priority: 0}) + it('returns a PriorityNode', function() { + expect(level0.setPriority(2) instanceof PriorityNode).to.be.true + }) + it('returns the correct PriorityNode', function() { + expect(level0.setPriority(3)).to.be.deep.equal(level0) + }) + it( + 'returns a PriorityNode with a retrievable data property', + function() { + expect(level0.setPriority(4).getData()).to.be.equal(0) + } + ) + it( + 'returns a PriorityNode with a retrievable new priority property', + function() { + expect(level0.setPriority(5).getPriority()).to.be.equal(5) + } + ) + it( + 'fails to change priority (from last value), if newPriority is invalid', + function() { + expect(level0.setPriority("6").getPriority()).to.be.equal(5) + } + ) + }) + + context('setNext()', function() { + const level0 = new PriorityNode({data: 0, priority: 0}) + const levelZero = new PriorityNode({data: 'level0', priority: 0}) + it('returns a PriorityNode', function() { + expect(level0.setNext(levelZero) instanceof PriorityNode).to.be.true + }) + it('returns the correct PriorityNode', function() { + expect(level0.setNext(levelZero)).to.be.deep.equal(level0) + }) + it( + 'returns a PriorityNode with a retrievable priority property', + function() { + expect(level0.setNext(levelZero).getPriority()).to.be.equal(0) + } + ) + it( + 'returns a PriorityNode with a retrievable new next property', + function() { + expect(level0.setNext(levelZero).getNext()).to.be.deep.equal(levelZero) + } + ) + it( + 'fails to change next (from last value), if newNext is invalid', + function() { + expect(level0.setNext("next00").getNext()).to.be.deep.equal(levelZero) + } + ) + }) + +}) diff --git a/spec/priority_queue.js b/spec/priority_queue.js new file mode 100644 index 0000000..2c9ee87 --- /dev/null +++ b/spec/priority_queue.js @@ -0,0 +1,72 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import PriorityQueue from '../src/priority_queue' + +chai.use(chaiChange) + +describe('PriorityQueue', function() { + 'use strict' + + it('is a function', function() { + expect(PriorityQueue).to.be.a('function') + }) + + context('empty queue', function() { + const emptyQueue = new PriorityQueue() + it('front() returns null', function() { + expect(emptyQueue.front()).to.be.null + }) + it('back() returns null', function() { + expect(emptyQueue.back()).to.be.null + }) + it('isEmpty() returns true', function() { + expect(emptyQueue.isEmpty()).to.be.true + }) + it('length() returns 0', function() { + expect(emptyQueue.length()).to.be.equal(0) + }) + it('dequeue() returns null', function() { + expect(emptyQueue.dequeue()).to.be.null + }) + }) + + context('non-empty queue', function() { + const pQueue = new PriorityQueue() + pQueue.enqueue('bad', 10) + pQueue.enqueue('ok', 50) + pQueue.enqueue('good', 90) + it('the element front() returns has priority 90', function() { + expect(pQueue.front().getPriority()).to.be.equal(90) + }) + it('the element front() returns has data “good”', function() { + expect(pQueue.front().getData()).to.be.equal('good') + }) + it('the element back() returns has priority 10', function() { + expect(pQueue.back().getPriority()).to.be.equal(10) + }) + it('isEmpty() returns false', function() { + expect(pQueue.isEmpty()).to.be.false + }) + it('length() returns 3', function() { + expect(pQueue.length()).to.be.equal(3) + }) + it('the element dequeue() returns has priority 90', function() { + expect(pQueue.dequeue().getPriority()).to.be.equal(90) + }) + it('the length is 2 after 1 dequeuing', function() { + expect(pQueue.length()).to.be.equal(2) + }) + it('the highest priority is 50 after 1 dequeuing', function() { + expect(pQueue.front().getPriority()).to.be.equal(50) + }) + it('the length is 3 after a valid enqueuing', function() { + pQueue.enqueue('horrible', 0); + expect(pQueue.length()).to.be.equal(3) + }) + it('the length stays 3 after an invalid enqueuing', function() { + pQueue.enqueue('wonderful', '100'); + expect(pQueue.length()).to.be.equal(3) + }) + }) + +}) diff --git a/spec/queue.js b/spec/queue.js new file mode 100644 index 0000000..0dc4581 --- /dev/null +++ b/spec/queue.js @@ -0,0 +1,66 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import Queue from '../src/queue' + +chai.use(chaiChange) + +describe('Queue', function() { + 'use strict' + + it('is a function', function() { + expect(Queue).to.be.a('function') + }) + + context('empty queue', function() { + const emptyQueue = new Queue() + it('front() returns null', function() { + expect(emptyQueue.front()).to.be.null + }) + it('back() returns null', function() { + expect(emptyQueue.back()).to.be.null + }) + it('isEmpty() returns true', function() { + expect(emptyQueue.isEmpty()).to.be.true + }) + it('length() returns 0', function() { + expect(emptyQueue.length()).to.be.equal(0) + }) + it('dequeue() returns null', function() { + expect(emptyQueue.dequeue()).to.be.null + }) + }) + + context('non-empty queue', function() { + const sQueue = new Queue() + sQueue.enqueue('bad') + sQueue.enqueue('ok') + sQueue.enqueue('good') + it('the element back() returns is “good”', function() { + expect(sQueue.back().getData()).to.be.equal('good') + }) + it('the element front() returns is “bad”', function() { + expect(sQueue.front().getData()).to.be.equal('bad') + }) + it('isEmpty() returns false', function() { + expect(sQueue.isEmpty()).to.be.false + }) + it('length() returns 3', function() { + expect(sQueue.length()).to.be.equal(3) + }) + it('the element dequeue() returns is “bad”', function() { + expect(sQueue.dequeue().getData()).to.be.equal('bad') + }) + it('the length is 2 after 1 dequeuing', function() { + expect(sQueue.length()).to.be.equal(2) + }) + it('the length is 3 after a valid enqueuing', function() { + sQueue.enqueue('excellent') + expect(sQueue.length()).to.be.equal(3) + }) + it('the length stays 3 after an invalid enqueuing', function() { + sQueue.enqueue() + expect(sQueue.length()).to.be.equal(3) + }) + }) + +}) diff --git a/spec/set.js-dev b/spec/set.js-dev new file mode 100644 index 0000000..48f2c6b --- /dev/null +++ b/spec/set.js-dev @@ -0,0 +1,81 @@ +import chai, { expect } from 'chai' +import chaiChange from 'chai-change' +import Set from '../src/set' + +chai.use(chaiChange) + +describe('Set', function() { + 'use strict' + + it('is a function', function() { + expect(Set).to.be.a('function') + }) + + context('empty set interrogation', function() { + const emptySet = new Set([]) + it('isEmpty() returns true', function() { + expect(emptySet.isEmpty()).to.be.true + }) + it('contains("x") returns false', function() { + expect(emptySet.contains('x')).to.be.false + }) + it('size() returns 0', function() { + expect(emptySet.size()).to.be.equal(0) + }) + }) + + context('non-empty set interrogation', function() { + const set = new Set([1, 2, 4, 8, 16]) + it('isEmpty() returns false', function() { + expect(set.isEmpty()).to.be.false + }) + it('contains(4) returns true', function() { + expect(set.contains(4)).to.be.true + }) + it('contains(5) returns false', function() { + expect(set.contains(5)).to.be.false + }) + it('size() returns 5', function() { + expect(set.size()).to.be.equal(5) + }) + }) + + context('dual-set interrogation', function() { + const set0 = new Set([3, 6, 9, 12, 15]) + const set1 = new Set([3, 6, 12, 24]) + it(('set0.union(set1) returns a set of 6 elements'), function() { + expect(set0.union(set1).size()).to.be.equal(6) + }) + it(('set0.union(set1) returns a set containing 15'), function() { + expect(set0.union(set1).contains(15)).to.be.true + }) + it(('set0.intersect(set1) returns a set of 3 elements'), function() { + expect(set0.intersect(set1).size()).to.be.equal(3) + }) + it(('set0.intersect(set1) returns a set containing 6'), function() { + expect(set0.intersect(set1).contains(6)).to.be.true + }) + it(('set0.intersect(set1) returns a set not containing 9'), function() { + expect(set0.intersect(set1).contains(9)).to.be.false + }) + it(('set0.difference(set1) returns a set of 2 elements'), function() { + expect(set0.intersect(set1).size()).to.be.equal(2) + }) + it(('set0.difference(set1) returns a set containing 9'), function() { + expect(set0.intersect(set1).contains(9)).to.be.true + }) + it(('set0.difference(set1) returns a set not containing 6'), function() { + expect(set0.intersect(set1).contains(6)).to.be.false + }) + it(('set1.difference(set0) returns a set of 1 element'), function() { + expect(set0.intersect(set1).size()).to.be.equal(2) + }) + it(('set1.difference(set0) returns a set containing 24'), function() { + expect(set0.intersect(set1).contains(24)).to.be.true + }) + it(('set1.difference(set0) returns a set not containing 6'), function() { + expect(set0.intersect(set1).contains(6)).to.be.false + }) + }) + +}) diff --git a/spec/stack.js b/spec/stack.js index 743122a..dfd9994 100644 --- a/spec/stack.js +++ b/spec/stack.js @@ -4,19 +4,57 @@ import Stack from '../src/stack' chai.use(chaiChange) -describe('Stack', () => { +describe('Stack', function() { 'use strict' - it('exists', () => { + it('is a function', function() { expect(Stack).to.be.a('function') }) - context('push()', () => { - it('pushes an element to the top of the stack.', () => { - const myStack = new Stack() + context('empty stack', function() { + const emptyStack = new Stack() + it('pop() returns null', function() { + expect(emptyStack.pop()).to.be.null + }) + it('peek() returns null', function() { + expect(emptyStack.peek()).to.be.null + }) + it('isEmpty() returns true', function() { + expect(emptyStack.isEmpty()).to.be.true + }) + it('length() returns 0', function() { + expect(emptyStack.length()).to.be.equal(0) + }) + }) - expect(() => myStack.push('foo')) - .to.alter(() => myStack.length(), { from: 0, to: 1 }) + context('non-empty stack', function() { + const sStack = new Stack() + sStack.push('bad') + sStack.push('ok') + sStack.push('good') + it('the element pop() returns is “good”', function() { + expect(sStack.pop().getData()).to.be.equal('good') + }) + it('the length is 2 after 1 pop', function() { + expect(sStack.length()).to.be.equal(2) + }) + it('the element peek() returns after a pop is “ok”', function() { + expect(sStack.peek().getData()).to.be.equal('ok') + }) + it('isEmpty() returns false', function() { + expect(sStack.isEmpty()).to.be.false + }) + it('length() after a pop returns 2', function() { + expect(sStack.length()).to.be.equal(2) + }) + it('the length is 3 after a valid push', function() { + sStack.push('good') + expect(sStack.length()).to.be.equal(3) + }) + it('the length stays 3 after an invalid push', function() { + sStack.push() + expect(sStack.length()).to.be.equal(3) }) }) + }) diff --git a/src/double_node.js b/src/double_node.js new file mode 100644 index 0000000..8a98fb8 --- /dev/null +++ b/src/double_node.js @@ -0,0 +1,53 @@ +'use strict'; + +/* + Class declaration for DoubleNode and export statement making that + object the default export from this module. +*/ +export default class DoubleNode { + constructor(nodeProps) { + // If the argument is valid: + if ( + nodeProps !== undefined + && nodeProps.data !== undefined + && ( + nodeProps.next === undefined + || nodeProps.next instanceof DoubleNode + ) + && ( + nodeProps.previous === undefined + || nodeProps.previous instanceof DoubleNode + ) + ) { + this.data = nodeProps.data; + this.next = nodeProps.next; + this.previous = nodeProps.previous; + } + } + // Returns the node’s data. + getData() { + return this.data; + } + // Returns the next node, or null if none. + getNext() { + return this.next || null; + } + // Changes the next node, if specified, and returns this node. + setNext(newNext) { + if (newNext instanceof DoubleNode) { + this.next = newNext; + } + return this; + } + // Returns the previous node, or null if none. + getPrevious() { + return this.previous || null; + } + // Changes the previous node, if specified, and returns this node. + setPrevious(newPrevious) { + if (newPrevious instanceof DoubleNode) { + this.previous = newPrevious; + } + return this; + } +} diff --git a/src/doubly_linked_list.js b/src/doubly_linked_list.js new file mode 100644 index 0000000..6e9c667 --- /dev/null +++ b/src/doubly_linked_list.js @@ -0,0 +1,133 @@ +'use strict'; +import DoubleNode from './double_node'; + +/* + Class declaration for DoublyLinkedList and export statement making that + object the default export from this module. +*/ +export default class DoublyLinkedList { + constructor() { + this.list = []; + } + + // UTILITY METHODS + + /* + Returns the index of the first element with a data value identical to + the specified one, or -1 if no such element exists. “First” = having the + smallest index. The array contains the nodes, with the head (first) node + at index 0. + */ + firstMatchIndex(data) { + let fmIndex = 0; + while (fmIndex < this.list.length) { + if (this.list[fmIndex].getData() === data) { + return fmIndex; + } + else { + fmIndex++; + } + } + return -1; + } + + // INTERROGATION METHODS + + // Returns the head (first) node. + getHeadNode() { + return this.list[0] || null; + } + + // Returns the tail (last) node. + getTailNode() { + return this.list[this.list.length - 1] || null; + } + + /* + Returns whether the list contains an element with the specified data + value. + */ + contains(data) { + return this.firstMatchIndex(data) > -1; + } + + /* + Returns the first node containing an element with the specified data + value. + */ + find(data) { + return this.list[this.firstMatchIndex(data)] || -1; + } + + // Returns whether the list is empty. + isEmpty() { + return this.list.length === 0; + } + + // Returns the length of the list. + size() { + return this.list.length; + } + + // MANIPULATION METHODS + + // Adds a specified element at the tail (as the last element). + insert(d) { + if (d !== undefined) { + const newNode = new DoubleNode({data: d}); + this.list.push(newNode); + } + } + + // Adds a specified element at the head (as the first element). + insertFirst(d) { + if (d !== undefined) { + const newNode = new DoubleNode({data: d}); + this.list.unshift(newNode); + } + } + + /* + Adds a specified element before the first element with a specified + data value. + */ + insertBefore(d, n) { + if (d !== undefined && n !== undefined) { + const fmIndex = this.firstMatchIndex(d); + if (fmIndex > -1) { + const newNode = new DoubleNode({data: n}); + this.list.splice(fmIndex, 0, newNode); + } + } + } + + /* + Adds a specified element after the first element with a specified + data value. + */ + insertAfter(d, n) { + if (d !== undefined && n !== undefined) { + const fmIndex = this.firstMatchIndex(d); + if (fmIndex > -1) { + const newNode = new DoubleNode({data: n}); + this.list.splice(fmIndex + 1, 0, newNode); + } + } + } + + // Deletes the last (tail) element. + remove() { + this.list.pop(); + } + + // Deletes the first (head) element. + removeFirst() { + this.list.shift(); + } + + // Deletes all elements. + clear() { + this.list.splice(0); + } + +} diff --git a/src/linked_list1.js b/src/linked_list1.js new file mode 100644 index 0000000..90a73ef --- /dev/null +++ b/src/linked_list1.js @@ -0,0 +1,167 @@ +import Node from '../src/node1'; + +'use strict'; + +// A list of nodes that link to each other, like a daisy-chain. +export default class LinkedList { + constructor() { + this.head = null; + this.tail = null; + this.length = 0; + } + + getHeadNode(){ + return this.head; + } + + getTailNode() { + return this.tail; + } + + contains( fruit ){ + let currentNode = this.head; + + while( currentNode ) { + if ( currentNode.data === fruit) { + return true; + } + if ( currentNode === this.tail ) { + return false; + } else { + currentNode = currentNode.next; + } + } + } + + find( fruit ){ + let currentNode = this.head; + + while ( currentNode.next !== null ) { + if ( currentNode.data === fruit ){ + return currentNode; + } + currentNode = currentNode.next; + } + return -1; + } + + insert( fruit ){ + let newNode = new Node( fruit ); + let currentNode = this.head; + + if ( !currentNode && this.length === 0 ) { + this.head = newNode; + this.tail = newNode; + } else { + this.tail.next = newNode; + this.tail = newNode; + } + this.length++; + } + + insertFirst(fruit){ + let newNode = new Node( fruit ); + let currentNode = this.head; + + if ( !currentNode ) { + this.head = newNode; + this.tail = newNode; + } else { + newNode.next = this.head; + this.head = newNode; + } + currentNode = currentNode.next; + this.length++; + } + + insertBefore( target, data){ + let newNode = new Node( data ); + let currentNode = this.head; + + if ( this.length == 1 ) { + newNode.next = this.head; + this.head = newNode; + } + + while ( currentNode ) { + if ( currentNode.data == target) { + newNode.next = currentNode; + this.head = newNode; + return; + } + if ( currentNode.next.data == target ) { + newNode.next = currentNode.next; + currentNode.next = newNode; + return; + } + if ( currentNode == this.tail ){ + return 'non-existent node in this list.'; + } + else { + currentNode = currentNode.next; + } + + } + this.length++; + } + + insertAfter( target, data){ + let newNode = new Node( data ); + let currentNode = this.head; + + if ( this.length == 1 ) { + currentNode.next = newNode; + } + + while ( currentNode ) { + if ( currentNode.data == target) { + newNode = currentNode.next; + currentNode.next = newNode; + return; + } + if ( currentNode == this.tail ){ + return 'No fruit'; + } + else { + currentNode = currentNode.next; + } + } + this.length++; + } + + remove() { + let currentNode = this.head; + while( currentNode.next ) { + if( currentNode.next === this.tail){ + currentNode = this.tail; + currentNode.next = null; + } + this.length; + return; + } + } + + removeFirst() { + let currentNode = this.head; + this.head = currentNode.next; + this.length; + return; + } + + isEmpty() { + if ( this.head == null) { + return true; + } + return false; + } + + size() { + return this.length; + } + + clear() { + this.tail = null; + this.head = this.tail; + this.length = 0; + } +} diff --git a/src/linked_list2.js b/src/linked_list2.js new file mode 100644 index 0000000..3c833e0 --- /dev/null +++ b/src/linked_list2.js @@ -0,0 +1,133 @@ +'use strict'; +import Node from './node2'; + +/* + Class declaration for LinkedList and export statement making that + object the default export from this module. +*/ +export default class LinkedList { + constructor() { + this.list = []; + } + + // UTILITY METHODS + + /* + Returns the index of the first element with a data value identical to + the specified one, or -1 if no such element exists. “First” = having the + smallest index. The array contains the nodes, with the head (first) node + at index 0. + */ + firstMatchIndex(data) { + let fmIndex = 0; + while (fmIndex < this.list.length) { + if (this.list[fmIndex].getData() === data) { + return fmIndex; + } + else { + fmIndex++; + } + } + return -1; + } + + // INTERROGATION METHODS + + // Returns the head (first) node. + getHeadNode() { + return this.list[0] || null; + } + + // Returns the tail (last) node. + getTailNode() { + return this.list[this.list.length - 1] || null; + } + + /* + Returns whether the list contains an element with the specified data + value. + */ + contains(data) { + return this.firstMatchIndex(data) > -1; + } + + /* + Returns the first node containing an element with the specified data + value. + */ + find(data) { + return this.list[this.firstMatchIndex(data)] || -1; + } + + // Returns whether the list is empty. + isEmpty() { + return this.list.length === 0; + } + + // Returns the length of the list. + size() { + return this.list.length; + } + + // MANIPULATION METHODS + + // Adds a specified element at the tail (as the last element). + insert(d) { + if (d !== undefined) { + const newNode = new Node({data: d}); + this.list.push(newNode); + } + } + + // Adds a specified element at the head (as the first element). + insertFirst(d) { + if (d !== undefined) { + const newNode = new Node({data: d}); + this.list.unshift(newNode); + } + } + + /* + Adds a specified element before the first element with a specified + data value. + */ + insertBefore(d, n) { + if (d !== undefined && n !== undefined) { + const fmIndex = this.firstMatchIndex(d); + if (fmIndex > -1) { + const newNode = new Node({data: n}); + this.list.splice(fmIndex, 0, newNode); + } + } + } + + /* + Adds a specified element after the first element with a specified + data value. + */ + insertAfter(d, n) { + if (d !== undefined && n !== undefined) { + const fmIndex = this.firstMatchIndex(d); + if (fmIndex > -1) { + const newNode = new Node({data: n}); + this.list.splice(fmIndex + 1, 0, newNode); + } + } + } + + // Deletes the last (tail) element. + remove() { + this.list.pop(); + } + + // Deletes the first (head) element. + removeFirst() { + this.list.shift(); + } + + // Deletes all elements. + clear() { + this.list.splice(0); + } + +} diff --git a/src/node1.js b/src/node1.js new file mode 100644 index 0000000..950e42f --- /dev/null +++ b/src/node1.js @@ -0,0 +1,28 @@ +'use strict'; + +// A very basic data structure that can contain some value and a reference to another node. +export default class Node { + constructor( nodeData ) { + this.data = nodeData.data; + this.next = null; + } + + // returns the data ("apple") of the node + getData() { + return this.data; + } + + // changes the reference to the next node and returns the original node + setNext( element ) { + this.next = element; + return this; + } + + // returns the next node, or null if no next node + getNext() { + if ( this.next ) { + return this.next; + } + return null; + } +} diff --git a/src/node2.js b/src/node2.js new file mode 100644 index 0000000..99e8556 --- /dev/null +++ b/src/node2.js @@ -0,0 +1,37 @@ +'use strict'; + +/* + Class declaration for Node and export statement making that + object the default export from this module. +*/ +export default class Node { + constructor(nodeProps) { + // If the argument is valid: + if ( + nodeProps !== undefined + && nodeProps.data !== undefined + && ( + nodeProps.next === undefined + || nodeProps.next instanceof Node + ) + ) { + this.data = nodeProps.data; + this.next = nodeProps.next || null; + } + } + // Returns the node’s data. + getData() { + return this.data; + } + // Returns the next node, or null if none. + getNext() { + return this.next || null; + } + // Changes the next node, if specified, and returns this node. + setNext(newNext) { + if (newNext instanceof Node) { + this.next = newNext; + } + return this; + } +} diff --git a/src/power_set.js b/src/power_set.js new file mode 100644 index 0000000..dd89a0e --- /dev/null +++ b/src/power_set.js @@ -0,0 +1,101 @@ +'use strict'; + +/* + Class declaration for PowerSet, with a default constructor, and export + statement making that object the default export from this module. +*/ +export default class PowerSet { + constructor(iterableObject) { + this.pSet = new Set(iterableObject); + } + + // INTERROGATION METHODS + + // Returns whether the powerSet is empty. + isEmpty() { + return this.pSet.size === 0; + } + + // Returns whether the powerSet contains the specified element. + contains(value) { + return this.pSet.has(value); + } + + // Returns the count of elements. + size() { + return this.pSet.size; + } + + // DUAL-SET INTERROGATION METHODS + + // Returns the union of this and another powerSet. + union(otherPowerSet) { + const newPowerSet = new PowerSet(this.pSet); + const addToThis = function(element) { + this.add(element); + }; + otherPowerSet.pSet.forEach(addToThis, newPowerSet.pSet); + return newPowerSet; + } + + // Returns the intersection of this and another powerSet. + intersect(otherPowerSet) { + const newPowerSet = new PowerSet(); + const addToThis = function(element) { + if (otherPowerSet.contains(element)) { + this.add(element); + } + }; + this.pSet.forEach(addToThis, newPowerSet.pSet); + return newPowerSet; + } + + // Returns the difference of this from another powerSet. + difference(otherPowerSet) { + const newPowerSet = new PowerSet(); + const addToThis = function(element) { + if (! otherPowerSet.contains(element)) { + this.add(element); + } + }; + this.pSet.forEach(addToThis, newPowerSet.pSet); + return newPowerSet; + } + + // Returns whether this is a subset of another powerSet. + isSubset(otherPowerSet) { + for (let element of this.pSet) { + if (! otherPowerSet.contains(element)) { + return false; + } + } + return true; + } + + // MANIPULATION METHODS + + // Adds a specified element, if not already in the PowerSet. + add(value) { + if (value !== undefined) { + this.pSet.add(value); + } + } + + // Removes a specified element, if in the PowerSet. + remove(value) { + this.pSet.delete(value); + } + + // Returns a copy of the PowerSet. + clone() { + return new PowerSet(this.pSet); + } + + // ELEMENT ITERATION METHODS + + // Executes a specified function on each element. + forEach(fn) { + this.pSet.forEach(fn, this.pSet); + } + +} diff --git a/src/priority_node.js b/src/priority_node.js new file mode 100644 index 0000000..41fa77e --- /dev/null +++ b/src/priority_node.js @@ -0,0 +1,52 @@ +'use strict'; + +/* + Class declaration for PriorityNode and export statement making that + object the default export from this module. +*/ +export default class PriorityNode { + constructor(nodeProps) { + // If the argument is valid: + if ( + nodeProps.data !== undefined + && ( + nodeProps.priority === undefined + || typeof nodeProps.priority === 'number' + ) + && ( + nodeProps.next === undefined + || nodeProps.next instanceof PriorityNode + ) + ) { + this.data = nodeProps.data; + this.priority = nodeProps.priority || 0; + this.next = nodeProps.next || null; + } + } + // Returns the node’s data. + getData() { + return this.data; + } + // Returns the node’s priority. + getPriority() { + return this.priority; + } + // Returns the next node, or null if none. + getNext() { + return this.next || null; + } + // Changes the node’s priority and returns the node. + setPriority(newPriority) { + if (typeof newPriority === 'number') { + this.priority = newPriority; + } + return this; + } + // Changes the next node, if specified, and returns this node. + setNext(newNext) { + if (newNext instanceof PriorityNode) { + this.next = newNext; + } + return this; + } +} diff --git a/src/priority_queue.js b/src/priority_queue.js new file mode 100644 index 0000000..37c97fe --- /dev/null +++ b/src/priority_queue.js @@ -0,0 +1,50 @@ +'use strict'; +import PriorityNode from './priority_node'; + +/* + Class declaration for PriorityQueue and export statement making that + object the default export from this module. +*/ +export default class PriorityQueue { + constructor() { + this.queue = []; + } + // Returns the count of elements with priorities lower than specified. + lowerCount(priority) { + let lowerCount = 0; + while ( + lowerCount < this.queue.length + && this.queue[lowerCount].getPriority() < priority + ) { + lowerCount++; + } + return lowerCount; + } + // Adds a specified element to the queue. + enqueue(d, p) { + const newNode = new PriorityNode({data: d, priority: p}); + if (newNode.data !== undefined) { + this.queue.splice(this.lowerCount(p), 0, newNode); + } + } + // Returns the highest-priority node. + front() { + return this.queue[this.queue.length - 1] || null; + } + // Returns the lowest-priority node. + back() { + return this.queue[0] || null; + } + // Returns whether the queue is empty. + isEmpty() { + return this.queue.length === 0; + } + // Returns the count of elements. + length() { + return this.queue.length; + } + // Removes and returns a specified element from the queue. + dequeue() { + return this.queue.pop() || null; + } +} diff --git a/src/queue.js b/src/queue.js new file mode 100644 index 0000000..95d451e --- /dev/null +++ b/src/queue.js @@ -0,0 +1,42 @@ +'use strict'; +import Node from './node2'; + +/* + Class declaration for Queue and export statement making that object the + default export from this module. +*/ +export default class Queue { + constructor() { + this.queue = []; + } + // Adds a specified element to the back of the queue. + enqueue(d) { + const newNode = new Node({data: d}); + if (newNode.data !== undefined) { + this.queue.push(newNode); + } + } + /* + Removes and returns the front element from the queue. Returns null if + the queue is empty. + */ + dequeue() { + return this.queue.shift() || null; + } + // Returns the front node. + front() { + return this.queue[0] || null; + } + // Returns the back node. + back() { + return this.queue[this.queue.length - 1] || null; + } + // Returns whether the queue is empty. + isEmpty() { + return this.queue.length === 0; + } + // Returns the count of elements. + length() { + return this.queue.length; + } +} diff --git a/src/stack.js b/src/stack.js index dcd1d13..0ffb914 100644 --- a/src/stack.js +++ b/src/stack.js @@ -1,5 +1,38 @@ -'use strict' +'use strict'; +import Node from './node2'; +/* + Class declaration for Stack and export statement making that object the + default export from this module. +*/ export default class Stack { - // your code here + constructor() { + this.stack = []; + } + // Adds a specified element to the top of the stack. + push(d) { + const newNode = new Node({data: d}); + if (newNode.data !== undefined) { + this.stack.push(newNode); + } + } + /* + Removes and returns the top element from the stack. Returns null if + the stack is empty. + */ + pop() { + return this.stack.pop() || null; + } + // Returns the top node. + peek() { + return this.stack[this.stack.length - 1] || null; + } + // Returns whether the stack is empty. + isEmpty() { + return this.stack.length === 0; + } + // Returns the count of elements. + length() { + return this.stack.length; + } }