5
\$\begingroup\$

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.

http://jsbin.com/figay/1/edit?html,js,output

\$\endgroup\$
4
\$\begingroup\$

I like it, from a once over:

  • map, filter, reduce etc. are not the most efficient functions, they make for readable code, not for speedy sorting/searching. See this : http://jsperf.com/arraymap, a simply for loop beats everything else every time, this also goes for filter,reduce etc.
  • I would change around the first block, there is too much happening horizontally:

    return array.map(function (e) {
        return {
            element: e, 
            weight: weightedTests.map(function (weightedTest) {
                return weightedTest.test(e) * weightedTest.weight;
            }).reduce(function (previousValue, currentValue) { 
                return previousValue + currentValue; }, 0)
         };
     })
    
  • The sorting by weight I would do like this:

    //sort first by weight if possible
    var weightDifference = obj2.weight- obj1.weight;
    if (weightDifference) {
      return weightDifference;
    }
    
  • The last return 0 does not need to be in an else block

  • Other than that the code runs fine on JsHint, is well commented and easy to follow.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the comments. I agree completely on the formatting bit I think I just overlooked it after reformatting the bits around it. I like the way you handled the weighting too. Regarding performance on map/filter/reduce this isn't being used on large datasets so I'll likely keep them for readability purposes however I was under the impression they were supposed to be quite efficient. If I did need high performance what approach should I use here instead and how significant an impact would it have? \$\endgroup\$ – Chao Mar 6 '14 at 10:44
  • 1
    \$\begingroup\$ Updated my answer. \$\endgroup\$ – konijn Mar 6 '14 at 13:24
  • \$\begingroup\$ Thanks. Useful benchmarks, I think it's helped confirm that it's not a worry unless we vastly increase the datasets we are working with. \$\endgroup\$ – Chao Mar 6 '14 at 17:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.