├── .gitignore ├── .travis.yml ├── package.json ├── Gruntfile.js ├── README.md ├── doubly-linked-list.js └── test └── doubly-linked-list_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .*redcar 2 | node_modules 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "DoublyLinkedList", 3 | "author": { 4 | "name": "Andrew Jones", 5 | "email": "andrew@andrew-jones.com", 6 | "url": "http://andrew-jones.com" 7 | }, 8 | "version": "0.0.1", 9 | "private": true, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "grunt": "~0.4", 13 | "grunt-cli": "~0.1", 14 | "grunt-beautify": "git://github.com/terryweiss/grunt-beautify.git#FixForGrunt040", 15 | "grunt-contrib-nodeunit": "~0.1.2", 16 | "grunt-contrib-jshint": "~0.1.1" 17 | }, 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "https://github.com/andrewrjones/grunt-ssh/blob/master/LICENSE-MIT" 22 | } 23 | ], 24 | "main": "doubly-linked-list.js", 25 | "scripts": { 26 | "test": "grunt" 27 | } 28 | } -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | 'use strict'; 3 | 4 | // Project configuration. 5 | grunt.initConfig({ 6 | nodeunit: { 7 | files: ['test/**/*.js'] 8 | }, 9 | beautify: { 10 | files: '<%= jshint.files %>' 11 | }, 12 | jshint: { 13 | options: { 14 | curly: true, 15 | eqeqeq: true, 16 | immed: true, 17 | latedef: true, 18 | newcap: true, 19 | noarg: true, 20 | sub: true, 21 | undef: true, 22 | boss: true, 23 | eqnull: true, 24 | node: true, 25 | globals: { 26 | exports: true 27 | } 28 | }, 29 | files: ['Gruntfile.js', 'doubly-linked-list.js', 'test/**/*.js'] 30 | } 31 | }); 32 | 33 | grunt.loadNpmTasks('grunt-contrib-jshint'); 34 | grunt.loadNpmTasks('grunt-beautify'); 35 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 36 | 37 | // Default task. 38 | grunt.registerTask('default', ['jshint', 'nodeunit']); 39 | 40 | grunt.registerTask('tidy', ['beautify']); 41 | 42 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DoublyLinkedList 2 | ================ 3 | 4 | This is a Javascipt implementation of a [doubly linked list](http://en.wikipedia.org/wiki/Doubly_linked_list). 5 | 6 | I needed a doubly linked list for a project I was doing, and thought I would post the code here in case it was useful for anyone else. 7 | 8 | Works in both the browser and node.js. 9 | 10 | Synopsis 11 | -------- 12 | 13 | node.js. 14 | 15 | ```javascript 16 | var DLL = require('doubly-linked-list.js'); 17 | var list = new DLL.DoublyLinkedList(); 18 | 19 | list.append('data1'); 20 | node = list.append('data2'); 21 | list.append('data3'); 22 | 23 | size = list.size; // 3 24 | node.prev.data; // data1 25 | node.next.data; // data3 26 | 27 | node = list.item(1); 28 | data = node.data; // data2 29 | prev = node.prev; // data1 30 | next = node.next; // data3 31 | 32 | node = list.head(); 33 | data = node.data; // data1 34 | 35 | node = list.tail(); 36 | data = node.data; // data3 37 | 38 | list.prepend('data4'); 39 | 40 | size = list.size; // 4 41 | 42 | node = list.head(); 43 | data = node.data; // data4 44 | prev = node.prev; // null 45 | next = node.next; // data1 46 | 47 | node.append('frog'); 48 | appended = node.next; // frog 49 | next = appended.next; // data1 50 | 51 | next.remove(); 52 | afterRemoved = appended.next; // data2 53 | 54 | afterRemoved.prepend('bird'); 55 | prepended = appended.next; // bird 56 | ``` 57 | 58 | Browser. 59 | 60 | ```html 61 | 62 | 66 | ``` 67 | 68 | API 69 | --- 70 | 71 | ### new DoublyLinkedList() 72 | 73 | Creates a new DoublyLinkedList. Takes no arguments. 74 | 75 | ### append(data) 76 | 77 | Appends a node to the end of the list. Returns the node. 78 | 79 | See `item(index)` for notes on the structure of the node returned. 80 | 81 | ### prepend(data) 82 | 83 | Prepends a node to the end of the list. Returns the node. 84 | 85 | See `item(index)` for notes on the structure of the node returned. 86 | 87 | ### item(index) 88 | 89 | Returns the node at the specified index. The index starts at 0. 90 | 91 | *Note:* Nodes returned support various operations. See the section about node objects for a list of supported properties and methods. 92 | 93 | ### head() 94 | 95 | Returns the node at the head of the list. See `item(index)` for notes on the structure of the node returned. 96 | 97 | ### tail() 98 | 99 | Returns the node at the tail of the list. See `item(index)` for notes on the structure of the node returned. 100 | 101 | ### size() 102 | 103 | Returns the size of the list. 104 | 105 | ### Node Objects 106 | 107 | When a list/node operation returns a node, it is in fact an object with the following properties and methods: 108 | 109 | #### prev 110 | 111 | The node before the current one, or null if this node is the list head. 112 | 113 | #### next 114 | 115 | The node after the current one, or null if this node is the list tail. 116 | 117 | #### data 118 | 119 | The data stored in the node when it was created. 120 | 121 | #### remove() 122 | 123 | Removes the node from the list. 124 | 125 | #### append(data) 126 | 127 | Creates a new node with a data value of `data` and adds it to the list after the current node. Returns the new node. 128 | 129 | #### prepend(data) 130 | 131 | Creates a new node with a data value of `data` and adds it to the list before the current node. Returns the new node. 132 | 133 | Build status 134 | ------------ 135 | 136 | [![Build Status](https://secure.travis-ci.org/andrewrjones/doubly-linked-list-js.png)](http://travis-ci.org/andrewrjones/doubly-linked-list-js) 137 | 138 | More 139 | ---- 140 | 141 | https://github.com/playcraft/gamecore.js/blob/master/src/linkedlist.js 142 | -------------------------------------------------------------------------------- /doubly-linked-list.js: -------------------------------------------------------------------------------- 1 | (function (exports) { 2 | "use strict"; 3 | /* 4 | * Constructor. Takes no arguments. 5 | */ 6 | 7 | function DoublyLinkedList() { 8 | // pointer to first item 9 | this._head = null; 10 | // pointer to the last item 11 | this._tail = null; 12 | // length of list 13 | this._length = 0; 14 | } 15 | 16 | // Wraps data in a node object. 17 | DoublyLinkedList.prototype._createNewNode = function (data) { 18 | var list = this; 19 | 20 | var node = { 21 | data: data, 22 | next: null, 23 | prev: null, 24 | 25 | remove: function() { 26 | if (this.prev !== null) { 27 | this.prev.next = this.next; 28 | } 29 | 30 | if (this.next !== null) { 31 | this.next.prev = this.prev; 32 | } 33 | 34 | if (list._head === this) { 35 | list._head = this.next; 36 | } 37 | 38 | if (list._tail === this) { 39 | list._tail = this.prev; 40 | } 41 | 42 | list._length--; 43 | }, 44 | 45 | prepend: function(data) { 46 | if (list._head === this) { 47 | return list.prepend(data); 48 | } 49 | 50 | var newNode = list._createNewNode(data); 51 | newNode.prev = this.prev; 52 | newNode.next = this; 53 | this.prev.next = newNode; 54 | this.prev = newNode; 55 | 56 | list._length++; 57 | return newNode; 58 | }, 59 | 60 | append: function(data) { 61 | if (list._tail === this) { 62 | return list.append(data); 63 | } 64 | 65 | var newNode = list._createNewNode(data); 66 | newNode.prev = this; 67 | newNode.next = this.next; 68 | this.next.prev = newNode; 69 | this.next = newNode; 70 | 71 | list._length++; 72 | return newNode; 73 | } 74 | }; 75 | 76 | return node; 77 | }; 78 | 79 | /* 80 | * Appends a node to the end of the list. 81 | */ 82 | DoublyLinkedList.prototype.append = function (data) { 83 | var node = this._createNewNode(data); 84 | 85 | if (this._length === 0) { 86 | 87 | // first node, so all pointers to this 88 | this._head = node; 89 | this._tail = node; 90 | } else { 91 | 92 | // put on the tail 93 | this._tail.next = node; 94 | node.prev = this._tail; 95 | this._tail = node; 96 | } 97 | 98 | // update count 99 | this._length++; 100 | 101 | return node; 102 | }; 103 | 104 | /* 105 | * Prepends a node to the end of the list. 106 | */ 107 | DoublyLinkedList.prototype.prepend = function (data) { 108 | var node = this._createNewNode(data); 109 | 110 | if (this._head === null) { 111 | 112 | // we are empty, so this is the first node 113 | // use the same logic as append 114 | return this.append(data); 115 | } else { 116 | 117 | // place before head 118 | this._head.prev = node; 119 | node.next = this._head; 120 | this._head = node; 121 | } 122 | 123 | // update count 124 | this._length++; 125 | 126 | return node; 127 | }; 128 | 129 | /* 130 | * Returns the node at the specified index. The index starts at 0. 131 | */ 132 | DoublyLinkedList.prototype.item = function (index) { 133 | if (index >= 0 && index < this._length) { 134 | var node = this._head; 135 | while (index--) { 136 | node = node.next; 137 | } 138 | return node; 139 | } 140 | }; 141 | 142 | /* 143 | * Returns the node at the head of the list. 144 | */ 145 | DoublyLinkedList.prototype.head = function () { 146 | return this._head; 147 | }; 148 | 149 | /* 150 | * Returns the node at the tail of the list. 151 | */ 152 | DoublyLinkedList.prototype.tail = function () { 153 | return this._tail; 154 | }; 155 | 156 | /* 157 | * Returns the size of the list. 158 | */ 159 | DoublyLinkedList.prototype.size = function () { 160 | return this._length; 161 | }; 162 | 163 | /* 164 | * Removes the item at the index. 165 | */ 166 | DoublyLinkedList.prototype.remove = function (index) { 167 | throw "Not implemented"; 168 | }; 169 | 170 | exports.DoublyLinkedList = DoublyLinkedList; 171 | })(typeof exports === 'undefined' ? this['DLL'] = {} : exports); 172 | -------------------------------------------------------------------------------- /test/doubly-linked-list_test.js: -------------------------------------------------------------------------------- 1 | var DLL = require('../doubly-linked-list.js'); 2 | 3 | module.exports = { 4 | 'DoublyLinkedList': function (test) { 5 | "use strict"; 6 | 7 | test.expect(76); 8 | 9 | var list = new DLL.DoublyLinkedList(), 10 | node = null; 11 | 12 | test.ok(list); 13 | test.strictEqual(list.size(), 0); 14 | 15 | // append items to the list 16 | list.append('data1'); 17 | test.strictEqual(list.size(), 1); 18 | node = list.append('data2'); 19 | test.strictEqual(list.size(), 2); 20 | list.append('data3'); 21 | test.strictEqual(list.size(), 3); 22 | // check pointers of middle node 23 | test.strictEqual(node.prev.data, 'data1'); 24 | test.strictEqual(node.next.data, 'data3'); 25 | 26 | // get tail item 27 | node = list.tail(); 28 | test.ok(node); 29 | test.strictEqual(node.data, 'data3'); 30 | test.strictEqual(node.prev.data, 'data2'); 31 | test.strictEqual(node.next, null); 32 | 33 | // get at 2 34 | node = list.item(1); 35 | test.ok(node); 36 | test.strictEqual(node.data, 'data2'); 37 | // check pointers 38 | test.strictEqual(node.prev.data, 'data1'); 39 | test.strictEqual(node.next.data, 'data3'); 40 | 41 | // prepend an item 42 | node = list.prepend('data4'); 43 | test.strictEqual(node.prev, null); 44 | test.strictEqual(node.next.data, 'data1'); 45 | test.strictEqual(list.size(), 4); 46 | 47 | // get new item 48 | node = list.head(); 49 | test.ok(node); 50 | test.strictEqual(node.data, 'data4'); 51 | test.strictEqual(node.prev, null); 52 | test.strictEqual(node.next.data, 'data1'); 53 | 54 | // get at 2 55 | node = list.item(1); 56 | test.ok(node); 57 | test.strictEqual(node.data, 'data1'); 58 | // check pointers 59 | test.strictEqual(node.prev.data, 'data4'); 60 | test.strictEqual(node.next.data, 'data2'); 61 | 62 | // Test prepend on an empty list 63 | var prependList = new DLL.DoublyLinkedList(); 64 | prependList.prepend('cake'); 65 | test.strictEqual(prependList.size(), 1); 66 | test.strictEqual(prependList.head().data, 'cake'); 67 | 68 | // Test node removal 69 | var makeListOfSizeN = function(n) { 70 | list = new DLL.DoublyLinkedList(); 71 | 72 | for (var i = 0; i < n; i++) { 73 | list.append("thing" + i); 74 | } 75 | 76 | return list; 77 | }; 78 | 79 | var removalList = makeListOfSizeN(3); 80 | removalList.item(1).remove(); 81 | test.strictEqual(removalList.size(), 2); 82 | test.strictEqual(removalList.head().data, 'thing0'); 83 | test.strictEqual(removalList.head().prev, null); 84 | test.strictEqual(removalList.head().next.data, 'thing2'); 85 | test.strictEqual(removalList.tail().data, 'thing2'); 86 | test.strictEqual(removalList.tail().prev.data, 'thing0'); 87 | test.strictEqual(removalList.tail().next, null); 88 | 89 | removalList = makeListOfSizeN(3); 90 | removalList.item(0).remove(); 91 | test.strictEqual(removalList.size(), 2); 92 | test.strictEqual(removalList.head().data, 'thing1'); 93 | test.strictEqual(removalList.head().prev, null); 94 | test.strictEqual(removalList.head().next.data, 'thing2'); 95 | test.strictEqual(removalList.tail().data, 'thing2'); 96 | test.strictEqual(removalList.tail().prev.data, 'thing1'); 97 | test.strictEqual(removalList.tail().next, null); 98 | 99 | removalList = makeListOfSizeN(3); 100 | removalList.item(2).remove(); 101 | test.strictEqual(removalList.size(), 2); 102 | test.strictEqual(removalList.head().data, 'thing0'); 103 | test.strictEqual(removalList.head().prev, null); 104 | test.strictEqual(removalList.head().next.data, 'thing1'); 105 | test.strictEqual(removalList.tail().data, 'thing1'); 106 | test.strictEqual(removalList.tail().prev.data, 'thing0'); 107 | test.strictEqual(removalList.tail().next, null); 108 | 109 | removalList = makeListOfSizeN(2); 110 | removalList.item(0).remove(); 111 | test.strictEqual(removalList.size(), 1); 112 | test.strictEqual(removalList.head().data, 'thing1'); 113 | test.strictEqual(removalList.tail().data, 'thing1'); 114 | test.strictEqual(removalList.head().prev, null); 115 | test.strictEqual(removalList.head().next, null); 116 | 117 | removalList = makeListOfSizeN(2); 118 | removalList.item(1).remove(); 119 | test.strictEqual(removalList.size(), 1); 120 | test.strictEqual(removalList.head().data, 'thing0'); 121 | test.strictEqual(removalList.tail().data, 'thing0'); 122 | test.strictEqual(removalList.head().prev, null); 123 | test.strictEqual(removalList.head().next, null); 124 | 125 | removalList = makeListOfSizeN(1); 126 | removalList.item(0).remove(); 127 | test.strictEqual(removalList.size(), 0); 128 | test.strictEqual(removalList.head(), null); 129 | test.strictEqual(removalList.tail(), null); 130 | 131 | // Test prepending before a specific node 132 | prependList = makeListOfSizeN(2); 133 | prependList.tail().prepend('new'); 134 | var newNode = prependList.item(1); 135 | test.strictEqual(prependList.size(), 3); 136 | test.strictEqual(newNode.data, 'new'); 137 | test.strictEqual(newNode.prev.data, 'thing0'); 138 | test.strictEqual(newNode.next.data, 'thing1'); 139 | 140 | prependList = makeListOfSizeN(2); 141 | prependList.head().prepend('new'); 142 | test.strictEqual(prependList.size(), 3); 143 | newNode = prependList.head(); 144 | test.strictEqual(newNode.data, 'new'); 145 | test.strictEqual(newNode.next.data, 'thing0'); 146 | 147 | // Test appending after a specific node 148 | var appendList = makeListOfSizeN(2); 149 | appendList.head().append('new'); 150 | newNode = appendList.item(1); 151 | test.strictEqual(appendList.size(), 3); 152 | test.strictEqual(newNode.data, 'new'); 153 | test.strictEqual(newNode.prev.data, 'thing0'); 154 | test.strictEqual(newNode.next.data, 'thing1'); 155 | 156 | appendList = makeListOfSizeN(2); 157 | appendList.tail().append('new'); 158 | test.strictEqual(appendList.size(), 3); 159 | newNode = appendList.tail(); 160 | test.strictEqual(newNode.data, 'new'); 161 | test.strictEqual(newNode.prev.data, 'thing1'); 162 | 163 | test.done(); 164 | } 165 | }; --------------------------------------------------------------------------------