-
Notifications
You must be signed in to change notification settings - Fork 0
/
DoublyLinkedList.js
366 lines (319 loc) · 8.92 KB
/
DoublyLinkedList.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
const DNode = require('./DoublyLinkedNode');
const Stack = require('./Stack');
const defineProperty = Object.defineProperty;
/**
* Checks if a parameter is `undefined`
* @private
* @param {*} obj Object to be tested
* @returns {boolean}
*/
function isUndefined(obj) {
return typeof obj === 'undefined';
}
/**
* Checks if a parameter is a positive number
* @private
* @param {*} n Object to be tested
* @returns {boolean}
*/
function isPositiveNumber(n) {
return Number.isInteger(n) && n >= 0;
}
/**
* Validates a parameter to be a positive integer.
* @private
* @param {string} parameterName Parameter name to show in the error message.
* @returns {function} The function validator.
*/
function validatePositiveNumber(parameterName) {
/**
* Throws an exception if a parameter is not a positive number
* @param {*} n Element to be tested
* @throws {RangeError} parameter should be a positive number.
*/
return function throwIfNotPositiveNumber(n) {
if (!isPositiveNumber(n)) {
throw new RangeError(`"${parameterName}" should be an Integer greater than or equal to 0. Given: ${n}`);
}
}
}
/**
* DoublyLinkedList ADT.
* Inititalizes a new instance of a DoublyLinkedList
* @constructs DoublyLinkedList
*/
function DoublyLinkedList() {
/**
* Pointer to the first DNode in the DoublyLinkedList
* @name DoublyLinkedList#_first
* @type {(DNode|null)}
* @default null
*/
this._first;
defineProperty(this, '_first', {
writable: true,
value: null
});
/**
* Pointer to the last DNode in the DoublyLinkedList
* @name DoublyLinkedList#_last
* @type {(DNode|null)}
* @default null
*/
this._last;
defineProperty(this, '_last', {
writable: true,
value: null
});
/**
* Length of the DoublyLinkedList
* @name DoublyLinkedList#length
* @type {number}
* @readonly
*/
this.length;
defineProperty(this, 'length', {
get() {
/**
* @type {DNode}
* @private
*/
let currentNode = this._first;
let length = 0;
while (currentNode) {
length++;
currentNode = currentNode.getNext();
}
return length;
}
});
/**
* Helper property to keep track of the current iterator.
* @name DoublyLinkedList#_currentIterator
* @type {number}
* @default 0
* @private
*/
this._currentIterator;
defineProperty(this, '_currentIterator', {
writable: true,
value: 0
});
return this;
}
/**
* Returns the self instance as iterator.
* @function DoublyLinkedList#Symbol.iterator
* @example
* // const s = new Set();
* // const it = s[Symbol.iterator]();
* @returns {DoublyLinkedList} instance iterator.
*/
DoublyLinkedList.prototype[Symbol.iterator] = function () {
return this;
};
/**
* Returns the next value of the @@iterator
* @function DoublyLinkedList#next
* @returns {object} {value, done}
*/
DoublyLinkedList.prototype.next = function () {
if (this._currentIterator < this.length) {
return {
value: this.get(this._currentIterator++),
done: false
};
} else {
this._currentIterator = 0;
return {
value: undefined,
done: true
};
}
};
/**
* Checks if the DoublyLinkedList is empty.
* @function DoublyLinkedList#isEmpty
* @returns {boolean} Whether the list is empty or not.
*/
DoublyLinkedList.prototype.isEmpty = function () {
return this._first === null && this._last === null;
};
/**
* Returns the node at the 'index' position in the list.
* @function DoublyLinkedList#getNode
* @param {number=} [index = lastIndex] Index of the node
* @returns {DoublyLinkedList} The node the the 'index' position in the list or null if the list is empty.
* @throws {RangeError} when index is not a positive integer or list is empty.
*/
DoublyLinkedList.prototype.getNode = function (index) {
if (this.isEmpty()) {
throw new RangeError('DoublyLinkedList is empty.');
} else {
const lastIndex = this.length - 1;
if (isUndefined(index)) {
index = lastIndex;
} else {
validatePositiveNumber('index')(index);
index = (index <= lastIndex ? index : lastIndex);
}
let currentNode;
let currentIndex;
if (index <= Math.floor(lastIndex / 2)) { /* search from first to last */
currentNode = this._first;
currentIndex = 0;
while (currentNode.hasNext()) {
if (currentIndex === index) {
break;
}
currentIndex++;
currentNode = currentNode.getNext();
}
} else { /* search from last to first */
currentNode = this._last;
currentIndex = lastIndex;
while (currentNode.hasPrevious()) {
if (currentIndex === index) {
break;
}
currentIndex--;
currentNode = currentNode.getPrevious();
}
}
return currentNode;
}
};
/**
* Returns the value of a DNode at the 'index' position in the list.
* @function DoublyLinkedList#get
* @param {number=} [index = lastIndex] Index of the DNode to be retrieved.
* @returns {*} Value of the DNode.
*/
DoublyLinkedList.prototype.get = function (index) {
return this.getNode(index)._value;
};
/**
* Inserts a new DNode before the 'index' provided.
* @function DoublyLinkedList#insert
* @param {*} value Value to be inserted.
* @param {number=} index 'index' to place the new item. If not provided, item is inserted at the end of the list.
* @returns {DoublyLinkedList} instance
* @throws {RangeError} 'index' should be a positive number.
*/
DoublyLinkedList.prototype.insert = function (value, index) {
const newNode = new DNode(value);
if (this.isEmpty()) {
this._first = newNode;
this._last = newNode;
} else {
const currentNode = this.getNode(index);
if (index === 0) { /* insert before first */
newNode.setNext(this._first);
this._first.setPrevious(newNode);
this._first = newNode;
} else if (currentNode === this._last) { /* insert at the end */
newNode.setPrevious(this._last);
this._last.setNext(newNode);
this._last = newNode;
} else { /* insert before index */
const prevNode = currentNode.getPrevious();
prevNode.setNext(newNode);
newNode.setPrevious(prevNode);
newNode.setNext(currentNode);
currentNode.setPrevious(newNode);
}
}
return this;
};
/**
* Removes an item from the list at the 'index' position and returns its value.
* @function DoublyLinkedList#remove
* @param {number=} index Position of the item to be removed.
* @returns {*} Value of the DNode.
* @throws {RangeError} 'index' should be a positive number.
*/
DoublyLinkedList.prototype.remove = function (index) {
const length = this.length;
let nodeToRemove = this.getNode(index);
if (length === 1) {
this._first = null;
this._last = null;
} else if (nodeToRemove === this._first) {
const newFirst = this._first.getNext();
newFirst.setPrevious(null);
this._first = newFirst;
} else if (nodeToRemove === this._last) {
const newLast = this._last.getPrevious();
newLast.setNext(null);
this._last = newLast;
} else {
const prevNode = nodeToRemove.getPrevious();
const nextNode = nodeToRemove.getNext();
prevNode.setNext(nextNode);
nextNode.setPrevious(prevNode);
}
return nodeToRemove._value;
};
/**
* My lazy and hackish implementation for sort.
* Moves all elements to an array, sorts them and inserts them back in to the list.
* @function DoublyLinkedList#sort
* @returns {DoublyLinkedList} instance
*/
DoublyLinkedList.prototype.sort = function () {
const data = [];
while (!this.isEmpty()) {
data.push(this.remove());
}
data.sort();
data.forEach(i => this.insert(i));
return this;
};
/**
* Reverses the items in the doubly linked list
* @function DoublyLinkedList#reverse
* @returns {DoublyLinkedList} instance
*/
DoublyLinkedList.prototype.reverse = function () {
const stack = new Stack();
while (!this.isEmpty()) {
stack.push(this.remove(0));
}
while (!stack.isEmpty()) {
this.insert(stack.pop());
}
return this;
};
/**
* Returns a doubly linked list as Array.
* @function DoublyLinkedList#toArray
* @returns {Array} The doubly linked list as Array.
*/
DoublyLinkedList.prototype.toArray = function () {
const it = this[Symbol.iterator]();
return [...it];
};
/**
* Return the doubly linked list as string.
* @function DoublyLinkedList#toString
* @param {function=} [parser = String] A parser function.
* @returns {string} Doubly linked list as string
*/
DoublyLinkedList.prototype.toString = function (parser) {
let listAsString = '';
let currentNode = this._first;
if (parser && typeof parser !== 'function') {
throw new TypeError(`Expected 'parser' to be a function. Given: ${typeof parser}`);
} else {
parser = parser || String;
}
while (currentNode) {
listAsString += parser(currentNode);
if (currentNode.hasNext()) {
listAsString += ',';
}
currentNode = currentNode.getNext();
}
return listAsString;
};
module.exports = DoublyLinkedList;