4
\$\begingroup\$

I was wondering if there is a better, more elegant way to write the following JavaScript code block:

var searchString = 'ma'; var names = ['Mark Kennel', 'Hellen Smith', 'Jane Mary Annet', 'Peter']; var filteredNames = names.filter(name => { var words = name.toLowerCase().split(' '); for(var i = 0; i < words.length; i++) { if (words[i].indexOf(searchString.toLowerCase()) === 0 ) return true; } } 

This runs in a function that filters an autocomplete input results. The filtering rule is:

Show the names that have a word which begins with the searchString

The above code (with searchString = 'ma') should return an array with two names: 'Mark Kennel' and 'Jane Mary Annet'.

\$\endgroup\$

    4 Answers 4

    3
    \$\begingroup\$

    Well, better and more elegant are quite opinion based and depend on different things: a code can be better regarding performance but worse regarding clarity.

    That being said, I'd like to drop my two cents: since you just want to know if a given word begins with the input string (which is different from containing the input string) you can use lastIndexOf, setting fromIndex as 0. That way, you don't search the whole string, but just its beginning:

    var searchString = 'ma'; var names = ['Mark Kennel', 'Hellen Smith', 'Jane Mary Annet', 'Peter']; function filterNames(arr, str) { return arr.filter(function(thisName) { return thisName.toLowerCase().split(" ").some(function(d) { return d.lastIndexOf(str.toLowerCase(), 0) === 0 }) }) } console.log(filterNames(names, searchString))

    Another obvious option here is using startsWith. However, unlike lastIndexOf, startsWith don't wok on IE (if you care for that).

    \$\endgroup\$
      2
      \$\begingroup\$

      There is no need to call searchString.toLowerCase() every loop iteration. I'd suggest to do it once outside the loop.

      var searchStringLowCase = searchString.toLowerCase(); 

      Also instead of splitting the name we can make use of indexOf:

      var filteredNames = names.filter(function (name) { var index = name.toLowerCase().indexOf(searchStringLowCase); return index === 0 || name[index - 1] === ' '; }); 

      Several examples at jsperf.

      \$\endgroup\$
        2
        \$\begingroup\$

        RegExp

        When selecting text items it is always best to use regular expressions. They take a little time to master but there are plenty of online resources, editors, and testers that help.

        RegExps are available in many computer languages, but as with all standards there are many differences so be sure to use the correct language (ECMAScript / JavaScript) version.

        Words beginning with *

        For your select (AKA filter) you want to match any string that contains a word starting with "ma" or whatever. The regular expression would be /\bma/i

        • the / starts a expression.
        • the \b means word boundary
        • the ma the two characters to match
        • the / closes the expression
        • the i is a search flag meaning case insensitive

        You will need to build the expression as needed so use the constructor to create the expression

        // Note that the "\\" escape backslash creates a single "\" const exp = new RegExp("\\b" + searchString, "i"); 

        Putting it into a function, you use exp.test(str) which returns true or false depending on the str. Note that you can not just use exp.test as a callback for filter, names.filter(exp.test) will throw an error. You need to either bind the function to the expression or supply the context

        Thus the more elegant solution is

        function filterNamesStartingWith(startStr, names) { const exp = new RegExp("\\b" + startStr, "i"); return names.filter(name => exp.test(name)); } // or maybe you prefer function filterNamesStartingWith(startStr, names) { const exp = new RegExp("\\b" + startStr, "i"); return names.filter(exp.test, exp); } 
        \$\endgroup\$
          -1
          \$\begingroup\$

          You can also use includes()

          var searchString = 'ma'; var names = ['Mark Kennel', 'Hellen Smith', 'Jane Mary Annet', 'Peter']; var filteredNames = names.filter(name => { var words = name.toLowerCase().split(','); for(let key in words) { if(words[key].includes(searchString)) return true; } }); 
          \$\endgroup\$
          1
          • \$\begingroup\$You have presented an alternative solution, but haven't reviewed the code. Please explain your reasoning (how your solution works and why it is better than the original) so that the author and other readers can learn from your thought process.\$\endgroup\$
            – Summer
            CommentedJul 20, 2018 at 14:49

          Start asking to get answers

          Find the answer to your question by asking.

          Ask question

          Explore related questions

          See similar questions with these tags.