4
\$\begingroup\$

I'm trying to find patterns of different length in a given string. How can I improve it? Are there problems?

function find(pattern,str) {
   var  arr = [];
   for (i= 0;i<str.length;i++) {
      if(pattern== str.slice(i,pattern.length+i)) {
       arr.push([i,pattern.length-1+i]);
     };
  };
   if(!arr.length) { return false;} else { return arr;};
};


find('abfd','abfdffdabfdfaffab');
\$\endgroup\$
4
  • \$\begingroup\$ Is there any reason that you can't use a regular expression? \$\endgroup\$ Commented Sep 15, 2015 at 21:51
  • \$\begingroup\$ no, it is every thing from scratch, the question was write a function that finds a pattern of different size in a string and returns the beginning and ending index. \$\endgroup\$
    – user84574
    Commented Sep 15, 2015 at 21:55
  • \$\begingroup\$ Or just use .indexOf() and slice only when you find a match. \$\endgroup\$
    – jfriend00
    Commented Sep 15, 2015 at 21:55
  • \$\begingroup\$ This is one of those optimization questions where the right answer is almost certainly "don't try". The only obvious issue I see: you'll never find a match when (pattern.length+i > str.length) so there's no point wasting effort checking there. \$\endgroup\$
    – Speed8ump
    Commented Sep 15, 2015 at 21:59

2 Answers 2

2
\$\begingroup\$

Here are two different ways of doing this, one using repeated calls to .indexOf() and one using repeated calls with a regex to .exec(). The regex solution requires the pattern argument to be a regex compatible string or an actual RegExp object with the g flag on it.

The .indexOf() option should be much faster than what you have since it avoids repeated .slice() operations and .indexOf() as a library function should be faster than your own variation. The regex option is a lot more flexible since you can search for phrases that match a regular expression.

function find1(pattern,str) {
    var  arr = [];
    var i = 0;
    while ((i = str.indexOf(pattern, i)) > -1) {
        // got a match starting at i
        arr.push([i, i + pattern.length - 1]);
        i++;
    }
    return arr.length ? arr : false;
};

function find2(pattern,str) {
    var  arr = [], matches;
    if (typeof pattern === "string") {
    	pattern = new RegExp(pattern, "g");
    } else if (!(pattern instanceof RegExp)) {
        throw new Error("pattern must be string or RegExp object");
    }
        
    while (matches = pattern.exec(str)) {
        arr.push([matches.index, matches.index + matches[0].length - 1]);
    }
    return arr.length ? arr : false;
};


document.getElementById("run1").addEventListener("click", function(e) {
    var results = find1('abfd','abfdffdabfdfaffab');
    log(results);
});

document.getElementById("run2").addEventListener("click", function(e) {
    var results = find2('abfd','abfdffdabfdfaffab');
    log(results);
});

function log(x) {
    var d = document.createElement("div");
    d.innerHTML = JSON.stringify(x) + "<br>";
    document.body.appendChild(d);
}
<button id="run1">Run .indexOf()</button> 
<button id="run2">Run regex</button><br><br>

\$\endgroup\$
1
  • \$\begingroup\$ Note that the RegExp behaves quite differently from the original code, which treats all characters in the "pattern" literally. \$\endgroup\$ Commented Sep 15, 2015 at 23:07
1
\$\begingroup\$

Creating string slices in every iteration is probably not efficient.

You don't need to iterate until str.length, it would be enough until str.length - pattern.length. This also helps with nonsense input where the pattern string is longer than the searched string.

The solution of @jfriend using indexOf takes care of both of these issues.

Another important issue is the return value. It's not great to return two kinds of values: boolean or array. It's a strange design, and it makes the function harder to use. For example I cannot simply iterate over the results, I have to check for the value for first. I suggest to return an empty array when they're are no matches. The result will be much cleaner and easier to use. Note that an empty array is falsy in JavaScript, so you can still use it in a boolean context.

The word pattern may evoke a regular expression or something with wildcards. If recommend another name that suggests better a literal string. Perhaps "fragment".

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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