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:
parent
1e1aeefd93
commit
cd5d9bd5b9
|
@ -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;
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user