1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2024-06-25 23:03:15 +00:00

Code cleanup of Linked Lists (#5241)

* made private methods limited to module scope
* moved private methods to file bottom
* changed tests to run comperable array functions in parallel
* added comments
This commit is contained in:
Cameron Fischer 2020-12-09 04:46:35 -05:00 committed by GitHub
parent 1e1aeefd93
commit cd5d9bd5b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 99 additions and 75 deletions

View File

@ -24,40 +24,19 @@ LinkedList.prototype.clear = function() {
LinkedList.prototype.remove = function(value) { LinkedList.prototype.remove = function(value) {
if($tw.utils.isArray(value)) { if($tw.utils.isArray(value)) {
for(var t=0; t<value.length; t++) { for(var t=0; t<value.length; t++) {
this._removeOne(value[t]); _removeOne(this,value[t]);
} }
} else { } else {
this._removeOne(value); _removeOne(this,value);
} }
}; };
LinkedList.prototype._removeOne = function(value) {
var node = this.index[value];
if(node) {
node.prev.next = node.next;
node.next.prev = node.prev;
this.length -= 1;
// Point index to the next instance of the same value, maybe nothing.
this.index[value] = node.copy;
}
return node;
};
LinkedList.prototype._linkToEnd = function(node) {
// Sticks the given node onto the end of the list.
this.prev.next = node;
node.prev = this.prev;
this.prev = node;
node.next = this;
this.length += 1;
};
LinkedList.prototype.push = function(/* values */) { LinkedList.prototype.push = function(/* values */) {
for(var i = 0; i < arguments.length; i++) { for(var i = 0; i < arguments.length; i++) {
var value = arguments[i]; var value = arguments[i];
var node = {value: value}; var node = {value: value};
var preexistingNode = this.index[value]; var preexistingNode = this.index[value];
this._linkToEnd(node); _linkToEnd(this,node);
if(preexistingNode) { if(preexistingNode) {
// We want to keep pointing to the first instance, but we want // We want to keep pointing to the first instance, but we want
// to have that instance (or chain of instances) point to the // to have that instance (or chain of instances) point to the
@ -75,11 +54,11 @@ LinkedList.prototype.push = function(/* values */) {
LinkedList.prototype.pushTop = function(value) { LinkedList.prototype.pushTop = function(value) {
if($tw.utils.isArray(value)) { if($tw.utils.isArray(value)) {
for(var t=0; t<value.length; t++) { for(var t=0; t<value.length; t++) {
this._removeOne(value[t]); _removeOne(this,value[t]);
} }
this.push.apply(this, value); this.push.apply(this,value);
} else { } else {
var node = this._removeOne(value); var node = _removeOne(this,value);
if(!node) { if(!node) {
node = {value: value}; node = {value: value};
this.index[value] = node; this.index[value] = node;
@ -95,7 +74,7 @@ LinkedList.prototype.pushTop = function(value) {
this.index[value] = node.copy; this.index[value] = node.copy;
node.copy = undefined; node.copy = undefined;
} }
this._linkToEnd(node); _linkToEnd(this,node);
} }
}; };
@ -113,6 +92,27 @@ LinkedList.prototype.toArray = function() {
return output; return output;
}; };
function _removeOne(list,value) {
var node = list.index[value];
if(node) {
node.prev.next = node.next;
node.next.prev = node.prev;
list.length -= 1;
// Point index to the next instance of the same value, maybe nothing.
list.index[value] = node.copy;
}
return node;
};
function _linkToEnd(list,node) {
// Sticks the given node onto the end of the list.
list.prev.next = node;
node.prev = list.prev;
list.prev = node;
node.next = list;
list.length += 1;
};
exports.LinkedList = LinkedList; exports.LinkedList = LinkedList;
})(); })();

View File

@ -8,6 +8,10 @@ Tests the utils.LinkedList class.
LinkedList was built to behave exactly as $tw.utils.pushTop and LinkedList was built to behave exactly as $tw.utils.pushTop and
Array.prototype.push would behave with an array. Array.prototype.push would behave with an array.
Many of these tests function by performing operations on a LinkedList while
performing the equivalent actions on an array with the old utility methods.
Then we confirm that the two come out functionally identical.
\*/ \*/
(function(){ (function(){
@ -17,66 +21,88 @@ Array.prototype.push would behave with an array.
describe("LinkedList class tests", function() { describe("LinkedList class tests", function() {
// pushTops a value or array of values into both the array and linked list.
function pushTop(array, linkedList, valueOrValues) {
$tw.utils.pushTop(array, valueOrValues);
linkedList.pushTop(valueOrValues);
};
// pushes values into both the array and the linked list.
function push(array, linkedList/*, other values */) {
var values = Array.prototype.slice(arguments, 2);
array.push.apply(array, values);
linkedList.push.apply(linkedList, values);
};
// operates a remove action on an array and a linked list in parallel.
function remove(array, linkedList, valueOrValues) {
$tw.utils.removeArrayEntries(array, valueOrValues);
linkedList.remove(valueOrValues);
};
// compares an array and a linked list to make sure they match up
function compare(array, linkedList) {
expect(linkedList.toArray()).toEqual(array);
expect(linkedList.length).toBe(array.length);
};
it("can pushTop", function() { it("can pushTop", function() {
var array = [];
var list = new $tw.utils.LinkedList(); var list = new $tw.utils.LinkedList();
list.push('A', 'B', 'C'); push(array, list, 'A', 'B', 'C');
// singles // singles
list.pushTop('X'); pushTop(array, list, 'X');
list.pushTop('B'); pushTop(array, list, 'B');
expect(list.toArray()).toEqual(['A', 'C', 'X', 'B']); compare(array, list); // A C X B
expect(list.length).toBe(4);
//arrays //arrays
list.pushTop(['X', 'A', 'G', 'A']); pushTop(array, list, ['X', 'A', 'G', 'A']);
// If the pushedTopped list has duplicates, they go in unempeded. // If the pushedTopped list has duplicates, they go in unempeded.
expect(list.toArray()).toEqual(['C', 'B', 'X', 'A', 'G', 'A']); compare(array, list); // C B X A G A
expect(list.length).toBe(6);
}); });
it("can pushTop with tricky duplicates", function() { it("can pushTop with tricky duplicates", function() {
var array = [];
var list = new $tw.utils.LinkedList(); var list = new $tw.utils.LinkedList();
list.push('A', 'B', 'A', 'C', 'A', 'end'); push(array, list, 'A', 'B', 'A', 'C', 'A', 'end');
// If the original list contains duplicates, only one instance is cut // If the original list contains duplicates, only one instance is cut
list.pushTop('A'); pushTop(array, list, 'A');
expect(list.toArray()).toEqual(['B', 'A', 'C', 'A', 'end', 'A']); compare(array, list); // B A C A end A
expect(list.length).toBe(6);
// And the Llist properly knows the next 'A' to cut if pushed again // And the Llist properly knows the next 'A' to cut if pushed again
list.pushTop(['X', 'A']); pushTop(array, list, ['X', 'A']);
expect(list.toArray()).toEqual(['B', 'C', 'A', 'end', 'A', 'X', 'A']); compare(array, list); // B C A end A X A
expect(list.length).toBe(7);
// One last time, to make sure we maintain the linked chain of copies // One last time, to make sure we maintain the linked chain of copies
list.pushTop('A'); pushTop(array, list, 'A');
expect(list.toArray()).toEqual(['B', 'C', 'end', 'A', 'X', 'A', 'A']); compare(array, list); // B C end A X A A
expect(list.length).toBe(7);
}); });
it("can handle particularly nasty pushTop pitfall", function() { it("can handle particularly nasty pushTop pitfall", function() {
var array = [];
var list = new $tw.utils.LinkedList(); var list = new $tw.utils.LinkedList();
list.push('A', 'B', 'A', 'C'); push(array, list, 'A', 'B', 'A', 'C');
list.pushTop('A'); // BACA pushTop(array, list, 'A'); // BACA
list.pushTop('X'); // BACAX pushTop(array, list, 'X'); // BACAX
list.remove('A'); // BCAX remove(array, list, 'A'); // BCAX
list.pushTop('A'); // BCXA pushTop(array, list, 'A'); // BCXA
list.remove('A'); // BCX remove(array, list, 'A'); // BCX
// But! The way I initially coded the copy chains, a mystery A could // But! The way I initially coded the copy chains, a mystery A could
// hang around. // hang around.
expect(list.toArray()).toEqual(['B', 'C', 'X']); compare(array, list); // B C X
expect(list.length).toBe(3);
}); });
it("can push", function() { it("can push", function() {
var array = [];
var list = new $tw.utils.LinkedList(); var list = new $tw.utils.LinkedList();
list.push('A', 'B', 'C'); push(array, list, 'A', 'B', 'C');
// singles // singles
list.push('B'); push(array, list, 'B');
expect(list.toArray()).toEqual(['A', 'B', 'C', 'B']); compare(array, list); // A B C B
expect(list.length).toBe(4);
// multiple args // multiple args
list.push('A', 'B', 'C'); push(array, list, 'A', 'B', 'C');
expect(list.toArray()).toEqual(['A', 'B', 'C', 'B', 'A', 'B', 'C']); compare(array, list); // A B C B A B C
expect(list.length).toBe(7);
}); });
it("can clear", function() { it("can clear", function() {
@ -88,31 +114,29 @@ describe("LinkedList class tests", function() {
}); });
it("can remove", function() { it("can remove", function() {
var array = [];
var list = new $tw.utils.LinkedList(); var list = new $tw.utils.LinkedList();
list.push('A', 'x', 'C', 'x', 'D', 'x', 'E', 'x'); push(array, list, 'A', 'x', 'C', 'x', 'D', 'x', 'E', 'x');
// single // single
list.remove('x'); remove(array, list, 'x');
expect(list.toArray()).toEqual(['A', 'C', 'x', 'D', 'x', 'E', 'x']); compare(array, list); // A C x D x E x
expect(list.length).toBe(7);
// arrays // arrays
list.remove(['x', 'A', 'x']); remove(array, list, ['x', 'A', 'x']);
expect(list.toArray()).toEqual(['C', 'D', 'E', 'x']); compare(array, list); // C D E x
expect(list.length).toBe(4);
}); });
it('can ignore removal of nonexistent items', function() { it('can ignore removal of nonexistent items', function() {
var array = [];
var list = new $tw.utils.LinkedList(); var list = new $tw.utils.LinkedList();
list.push('A', 'B', 'C', 'D'); push(array, list, 'A', 'B', 'C', 'D');
// single // single
list.remove('Z'); remove(array, list, 'Z');
expect(list.toArray()).toEqual(['A', 'B', 'C', 'D']); compare(array, list); // A B C D
expect(list.length).toBe(4);
// array // array
list.remove(['Z', 'B', 'X']); remove(array, list, ['Z', 'B', 'X']);
expect(list.toArray()).toEqual(['A', 'C', 'D']); compare(array, list); // A C D
expect(list.length).toBe(3);
}); });
it('can iterate with each', function() { it('can iterate with each', function() {