I tried to make a weighted search function in JavaScript. I've been improving my JS lately but still not sure about some best practices, wondering if there's any improvements I could make, or if I've ended up doing it completely wrong.
The intention is to keep it quite flexible so it can take an array of filter functions, each with its own weight, it also takes a property on which to sort items with equal weights.
The search function is
function weightedSearch(array, weightedTests, sortProperty) {
return array.map(function (e) {
return {
element: e, weight: weightedTests.map(function (weightedTest) {
var testResult = weightedTest.test(e);
return testResult * weightedTest.weight;
}).reduce(function (previousValue, currentValue) { return previousValue + currentValue; }, 0)
};
}).filter(function (element) {
return element.weight > 0;
}).sort(function (obj1, obj2) {
//sort first by weight
if (obj1.weight > obj2.weight) {
return -1;
} else if (obj1.weight < obj2.weight) {
return 1;
}
// Else by chosen property
if (obj1.element[sortProperty] < obj2.element[sortProperty]) {
return -1;
} else if (obj1.element[sortProperty] > obj2.element[sortProperty]) {
return 1;
} else {
return 0;
}
}).map(function (e) {
return e.element;
});
}
A weighted test is an object of the form:
{test: function(element){}, weight: relativeWeightOfTheTest}
And would be used in a manner such as:
var search = "test";
var results = weightedSearch(arrayToSearch, [
{ test: function (testElement) { return testElement.title.toLowerCase().indexOf(search) >= 0; }, weight: 2 },
{ test: function (testElement) { return testElement.description.toLowerCase().indexOf(search) >= 0; }, weight: 0.5 }
], "title")
This would return an array where either the title or description contain "test" and ordered by:
- Appearing in title and description
- Appearing in title only
- Appearing in description only
Any results with the same weighting are then ordered by their title. Obviously this is a simple test case, you could easily have a test that adds up instances of the search string, the test could check it against a list of synonyms before counting etc.
Here's a JSbin with some test data and a quick Knockout usecase.