mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-23 10:07:19 +00:00
use a linked list for filter runs. (#5206)
* Changed the filterrunprefixes to use LinkedList * Testing for Linked List * Finishing touches to LinkedList * Minor corrections to link-list coding style * Corrected for sneaky bug in linkedList
This commit is contained in:
parent
c4dcf510ef
commit
a857b4ab9a
@ -18,7 +18,7 @@ Export our filter prefix function
|
|||||||
*/
|
*/
|
||||||
exports.all = function(operationSubFunction) {
|
exports.all = function(operationSubFunction) {
|
||||||
return function(results,source,widget) {
|
return function(results,source,widget) {
|
||||||
Array.prototype.push.apply(results,operationSubFunction(source,widget));
|
results.push.apply(results, operationSubFunction(source,widget));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,9 +19,9 @@ Export our filter prefix function
|
|||||||
exports.and = function(operationSubFunction,options) {
|
exports.and = function(operationSubFunction,options) {
|
||||||
return function(results,source,widget) {
|
return function(results,source,widget) {
|
||||||
// This replaces all the elements of the array, but keeps the actual array so that references to it are preserved
|
// This replaces all the elements of the array, but keeps the actual array so that references to it are preserved
|
||||||
source = options.wiki.makeTiddlerIterator(results);
|
source = options.wiki.makeTiddlerIterator(results.toArray());
|
||||||
results.splice(0,results.length);
|
results.clear();
|
||||||
$tw.utils.pushTop(results,operationSubFunction(source,widget));
|
results.pushTop(operationSubFunction(source,widget));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ exports.else = function(operationSubFunction) {
|
|||||||
return function(results,source,widget) {
|
return function(results,source,widget) {
|
||||||
if(results.length === 0) {
|
if(results.length === 0) {
|
||||||
// Main result so far is empty
|
// Main result so far is empty
|
||||||
$tw.utils.pushTop(results,operationSubFunction(source,widget));
|
results.pushTop(operationSubFunction(source,widget));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ Export our filter prefix function
|
|||||||
*/
|
*/
|
||||||
exports.except = function(operationSubFunction) {
|
exports.except = function(operationSubFunction) {
|
||||||
return function(results,source,widget) {
|
return function(results,source,widget) {
|
||||||
$tw.utils.removeArrayEntries(results,operationSubFunction(source,widget));
|
results.remove(operationSubFunction(source,widget));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,13 +17,13 @@ exports.filter = function(operationSubFunction,options) {
|
|||||||
return function(results,source,widget) {
|
return function(results,source,widget) {
|
||||||
if(results.length > 0) {
|
if(results.length > 0) {
|
||||||
var resultsToRemove = [];
|
var resultsToRemove = [];
|
||||||
$tw.utils.each(results,function(result) {
|
results.each(function(result) {
|
||||||
var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([result]),widget);
|
var filtered = operationSubFunction(options.wiki.makeTiddlerIterator([result]),widget);
|
||||||
if(filtered.length === 0) {
|
if(filtered.length === 0) {
|
||||||
resultsToRemove.push(result);
|
resultsToRemove.push(result);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$tw.utils.removeArrayEntries(results,resultsToRemove);
|
results.remove(resultsToRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,8 @@ exports.intersection = function(operationSubFunction) {
|
|||||||
return function(results,source,widget) {
|
return function(results,source,widget) {
|
||||||
if(results.length !== 0) {
|
if(results.length !== 0) {
|
||||||
var secondRunResults = operationSubFunction(source,widget);
|
var secondRunResults = operationSubFunction(source,widget);
|
||||||
var firstRunResults = results.splice(0);
|
var firstRunResults = results.toArray();
|
||||||
|
results.clear();
|
||||||
$tw.utils.each(firstRunResults,function(title) {
|
$tw.utils.each(firstRunResults,function(title) {
|
||||||
if(secondRunResults.indexOf(title) !== -1) {
|
if(secondRunResults.indexOf(title) !== -1) {
|
||||||
results.push(title);
|
results.push(title);
|
||||||
@ -27,4 +28,4 @@ exports.intersection = function(operationSubFunction) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -17,7 +17,7 @@ Export our filter prefix function
|
|||||||
*/
|
*/
|
||||||
exports.or = function(operationSubFunction) {
|
exports.or = function(operationSubFunction) {
|
||||||
return function(results,source,widget) {
|
return function(results,source,widget) {
|
||||||
$tw.utils.pushTop(results,operationSubFunction(source,widget));
|
results.pushTop(operationSubFunction(source,widget));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@ exports.reduce = function(operationSubFunction,options) {
|
|||||||
return function(results,source,widget) {
|
return function(results,source,widget) {
|
||||||
if(results.length > 0) {
|
if(results.length > 0) {
|
||||||
var accumulator = "";
|
var accumulator = "";
|
||||||
for(var index=0; index<results.length; index++) {
|
var index = 0;
|
||||||
var title = results[index],
|
results.each(function(title) {
|
||||||
list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
|
var list = operationSubFunction(options.wiki.makeTiddlerIterator([title]),{
|
||||||
getVariable: function(name) {
|
getVariable: function(name) {
|
||||||
switch(name) {
|
switch(name) {
|
||||||
case "currentTiddler":
|
case "currentTiddler":
|
||||||
@ -39,8 +39,9 @@ exports.reduce = function(operationSubFunction,options) {
|
|||||||
if(list.length > 0) {
|
if(list.length > 0) {
|
||||||
accumulator = "" + list[0];
|
accumulator = "" + list[0];
|
||||||
}
|
}
|
||||||
}
|
++index;
|
||||||
results.splice(0,results.length);
|
});
|
||||||
|
results.clear();
|
||||||
results.push(accumulator);
|
results.push(accumulator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -311,11 +311,11 @@ exports.compileFilter = function(filterString) {
|
|||||||
} else if(typeof source === "object") { // Array or hashmap
|
} else if(typeof source === "object") { // Array or hashmap
|
||||||
source = self.makeTiddlerIterator(source);
|
source = self.makeTiddlerIterator(source);
|
||||||
}
|
}
|
||||||
var results = [];
|
var results = new $tw.utils.LinkedList();
|
||||||
$tw.utils.each(operationFunctions,function(operationFunction) {
|
$tw.utils.each(operationFunctions,function(operationFunction) {
|
||||||
operationFunction(results,source,widget);
|
operationFunction(results,source,widget);
|
||||||
});
|
});
|
||||||
return results;
|
return results.toArray();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
118
core/modules/utils/linked-list.js
Normal file
118
core/modules/utils/linked-list.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*\
|
||||||
|
module-type: utils
|
||||||
|
title: $:/core/modules/utils/linkedlist.js
|
||||||
|
type: application/javascript
|
||||||
|
|
||||||
|
This is a doubly-linked indexed list intended for manipulation, particularly
|
||||||
|
pushTop, which it does with significantly better performance than an array.
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
function LinkedList() {
|
||||||
|
this.clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
LinkedList.prototype.clear = function() {
|
||||||
|
this.index = Object.create(null);
|
||||||
|
// LinkedList performs the duty of both the head and tail node
|
||||||
|
this.next = this;
|
||||||
|
this.prev = this;
|
||||||
|
this.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
LinkedList.prototype.remove = function(value) {
|
||||||
|
if($tw.utils.isArray(value)) {
|
||||||
|
for(var t=0; t<value.length; t++) {
|
||||||
|
this._removeOne(value[t]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._removeOne(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 */) {
|
||||||
|
for(var i = 0; i < arguments.length; i++) {
|
||||||
|
var value = arguments[i];
|
||||||
|
var node = {value: value};
|
||||||
|
var preexistingNode = this.index[value];
|
||||||
|
this._linkToEnd(node);
|
||||||
|
if(preexistingNode) {
|
||||||
|
// We want to keep pointing to the first instance, but we want
|
||||||
|
// to have that instance (or chain of instances) point to the
|
||||||
|
// new one.
|
||||||
|
while (preexistingNode.copy) {
|
||||||
|
preexistingNode = preexistingNode.copy;
|
||||||
|
}
|
||||||
|
preexistingNode.copy = node;
|
||||||
|
} else {
|
||||||
|
this.index[value] = node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LinkedList.prototype.pushTop = function(value) {
|
||||||
|
if($tw.utils.isArray(value)) {
|
||||||
|
for(var t=0; t<value.length; t++) {
|
||||||
|
this._removeOne(value[t]);
|
||||||
|
}
|
||||||
|
this.push.apply(this, value);
|
||||||
|
} else {
|
||||||
|
var node = this._removeOne(value);
|
||||||
|
if(!node) {
|
||||||
|
node = {value: value};
|
||||||
|
this.index[value] = node;
|
||||||
|
} else {
|
||||||
|
// Put this node at the end of the copy chain.
|
||||||
|
var preexistingNode = node;
|
||||||
|
while(preexistingNode.copy) {
|
||||||
|
preexistingNode = preexistingNode.copy;
|
||||||
|
}
|
||||||
|
// The order of these three statements is important,
|
||||||
|
// because sometimes preexistingNode == node.
|
||||||
|
preexistingNode.copy = node;
|
||||||
|
this.index[value] = node.copy;
|
||||||
|
node.copy = undefined;
|
||||||
|
}
|
||||||
|
this._linkToEnd(node);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LinkedList.prototype.each = function(callback) {
|
||||||
|
for(var ptr = this.next; ptr !== this; ptr = ptr.next) {
|
||||||
|
callback(ptr.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LinkedList.prototype.toArray = function() {
|
||||||
|
var output = [];
|
||||||
|
for(var ptr = this.next; ptr !== this; ptr = ptr.next) {
|
||||||
|
output.push(ptr.value);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.LinkedList = LinkedList;
|
||||||
|
|
||||||
|
})();
|
130
editions/test/tiddlers/tests/test-linked-list.js
Normal file
130
editions/test/tiddlers/tests/test-linked-list.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
/*\
|
||||||
|
title: test-linked-list.js
|
||||||
|
type: application/javascript
|
||||||
|
tags: [[$:/tags/test-spec]]
|
||||||
|
|
||||||
|
Tests the utils.LinkedList class.
|
||||||
|
|
||||||
|
LinkedList was built to behave exactly as $tw.utils.pushTop and
|
||||||
|
Array.prototype.push would behave with an array.
|
||||||
|
|
||||||
|
\*/
|
||||||
|
(function(){
|
||||||
|
|
||||||
|
/*jslint node: true, browser: true */
|
||||||
|
/*global $tw: false */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
describe("LinkedList class tests", function() {
|
||||||
|
|
||||||
|
it("can pushTop", function() {
|
||||||
|
var list = new $tw.utils.LinkedList();
|
||||||
|
list.push('A', 'B', 'C');
|
||||||
|
// singles
|
||||||
|
list.pushTop('X');
|
||||||
|
list.pushTop('B');
|
||||||
|
expect(list.toArray()).toEqual(['A', 'C', 'X', 'B']);
|
||||||
|
expect(list.length).toBe(4);
|
||||||
|
//arrays
|
||||||
|
list.pushTop(['X', 'A', 'G', 'A']);
|
||||||
|
// If the pushedTopped list has duplicates, they go in unempeded.
|
||||||
|
expect(list.toArray()).toEqual(['C', 'B', 'X', 'A', 'G', 'A']);
|
||||||
|
expect(list.length).toBe(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can pushTop with tricky duplicates", function() {
|
||||||
|
var list = new $tw.utils.LinkedList();
|
||||||
|
list.push('A', 'B', 'A', 'C', 'A', 'end');
|
||||||
|
// If the original list contains duplicates, only one instance is cut
|
||||||
|
list.pushTop('A');
|
||||||
|
expect(list.toArray()).toEqual(['B', 'A', 'C', 'A', 'end', 'A']);
|
||||||
|
expect(list.length).toBe(6);
|
||||||
|
|
||||||
|
// And the Llist properly knows the next 'A' to cut if pushed again
|
||||||
|
list.pushTop(['X', 'A']);
|
||||||
|
expect(list.toArray()).toEqual(['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
|
||||||
|
list.pushTop('A');
|
||||||
|
expect(list.toArray()).toEqual(['B', 'C', 'end', 'A', 'X', 'A', 'A']);
|
||||||
|
expect(list.length).toBe(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can handle particularly nasty pushTop pitfall", function() {
|
||||||
|
var list = new $tw.utils.LinkedList();
|
||||||
|
list.push('A', 'B', 'A', 'C');
|
||||||
|
list.pushTop('A'); // BACA
|
||||||
|
list.pushTop('X'); // BACAX
|
||||||
|
list.remove('A'); // BCAX
|
||||||
|
list.pushTop('A'); // BCXA
|
||||||
|
list.remove('A'); // BCX
|
||||||
|
// But! The way I initially coded the copy chains, a mystery A could
|
||||||
|
// hang around.
|
||||||
|
expect(list.toArray()).toEqual(['B', 'C', 'X']);
|
||||||
|
expect(list.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can push", function() {
|
||||||
|
var list = new $tw.utils.LinkedList();
|
||||||
|
list.push('A', 'B', 'C');
|
||||||
|
// singles
|
||||||
|
list.push('B');
|
||||||
|
expect(list.toArray()).toEqual(['A', 'B', 'C', 'B']);
|
||||||
|
expect(list.length).toBe(4);
|
||||||
|
|
||||||
|
// multiple args
|
||||||
|
list.push('A', 'B', 'C');
|
||||||
|
expect(list.toArray()).toEqual(['A', 'B', 'C', 'B', 'A', 'B', 'C']);
|
||||||
|
expect(list.length).toBe(7);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can clear", function() {
|
||||||
|
var list = new $tw.utils.LinkedList();
|
||||||
|
list.push('A', 'B', 'C');
|
||||||
|
list.clear();
|
||||||
|
expect(list.toArray()).toEqual([]);
|
||||||
|
expect(list.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can remove", function() {
|
||||||
|
var list = new $tw.utils.LinkedList();
|
||||||
|
list.push('A', 'x', 'C', 'x', 'D', 'x', 'E', 'x');
|
||||||
|
// single
|
||||||
|
list.remove('x');
|
||||||
|
expect(list.toArray()).toEqual(['A', 'C', 'x', 'D', 'x', 'E', 'x']);
|
||||||
|
expect(list.length).toBe(7);
|
||||||
|
|
||||||
|
// arrays
|
||||||
|
list.remove(['x', 'A', 'x']);
|
||||||
|
expect(list.toArray()).toEqual(['C', 'D', 'E', 'x']);
|
||||||
|
expect(list.length).toBe(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can ignore removal of nonexistent items', function() {
|
||||||
|
var list = new $tw.utils.LinkedList();
|
||||||
|
list.push('A', 'B', 'C', 'D');
|
||||||
|
// single
|
||||||
|
list.remove('Z');
|
||||||
|
expect(list.toArray()).toEqual(['A', 'B', 'C', 'D']);
|
||||||
|
expect(list.length).toBe(4);
|
||||||
|
|
||||||
|
// array
|
||||||
|
list.remove(['Z', 'B', 'X']);
|
||||||
|
expect(list.toArray()).toEqual(['A', 'C', 'D']);
|
||||||
|
expect(list.length).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can iterate with each', function() {
|
||||||
|
var list = new $tw.utils.LinkedList();
|
||||||
|
list.push('0', '1', '2', '3');
|
||||||
|
var counter = 0;
|
||||||
|
list.each(function(value) {
|
||||||
|
expect(value).toBe(counter.toString());
|
||||||
|
counter = counter + 1;
|
||||||
|
});
|
||||||
|
expect(counter).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
})();
|
Loading…
Reference in New Issue
Block a user