Categories
javascript string string-matching substring

How to check whether a string contains a substring in JavaScript?

7414

Usually I would expect a String.contains() method, but there doesn’t seem to be one.

What is a reasonable way to check for this?

0

    15320

    ECMAScript 6 introduced String.prototype.includes:

    const string = "foo";
    const substring = "oo";
    
    console.log(string.includes(substring)); // true

    includes doesn’t have Internet Explorer support, though. In ECMAScript 5 or older environments, use String.prototype.indexOf, which returns -1 when a substring cannot be found:

    var string = "foo";
    var substring = "oo";
    
    console.log(string.indexOf(substring) !== -1); // true

    6

    • 35

      While this is a good answer, and the OP never requested for a “case-sensitive” search, it should be noted that includes performs a case-sensitive search.

      – Gavin

      Jun 18, 2021 at 15:22

    • 5

      @Aashiq: Yes, an empty string is a substring of every string.

      – Ry-

      Sep 22, 2021 at 15:39

    • 6

      @Gavin by default if I want to know if something is a substring, I imagine it would be case-sensitive. After all, “A” and “a” are different characters. The OP never requested a “case-insensitive” search ( which is a trivial solution, if you make everything lowercase)

      – Davo

      Jan 15 at 1:31

    • indexOf is also case case-sensitive search, so both includes and indexOf are case-sensitive .

      Apr 13 at 0:21

    • 1

      Why is a discussion of case sensitivity even taking place here?

      – KWallace

      Jun 30 at 19:04

    736

    There is a String.prototype.includes in ES6:

    "potato".includes("to");
    > true
    

    Note that this does not work in Internet Explorer or some other old browsers with no or incomplete ES6 support. To make it work in old browsers, you may wish to use a transpiler like Babel, a shim library like es6-shim, or this polyfill from MDN:

    if (!String.prototype.includes) {
      String.prototype.includes = function(search, start) {
        'use strict';
        if (typeof start !== 'number') {
          start = 0;
        }
    
        if (start + search.length > this.length) {
          return false;
        } else {
          return this.indexOf(search, start) !== -1;
        }
      };
    }
    

    2

    • just curious, why do you need to check the length? Does IE fail in that case or something?

      – gman

      Feb 2, 2021 at 15:29

    • 1

      Also the checking for number fails to perform like includes. Example: es6 includes returns false for "abc".includes("ab", "1") this polyfill will return true

      – gman

      Feb 2, 2021 at 15:34


    94

    Another alternative is KMP (Knuth–Morris–Pratt).

    The KMP algorithm searches for a length-m substring in a length-n string in worst-case O(n+m) time, compared to a worst-case of O(nm) for the naive algorithm, so using KMP may be reasonable if you care about worst-case time complexity.

    Here’s a JavaScript implementation by Project Nayuki, taken from https://www.nayuki.io/res/knuth-morris-pratt-string-matching/kmp-string-matcher.js:

    // Searches for the given pattern string in the given text string using the Knuth-Morris-Pratt string matching algorithm.
    // If the pattern is found, this returns the index of the start of the earliest match in 'text'. Otherwise -1 is returned.
    

    function kmpSearch(pattern, text) {
      if (pattern.length == 0)
        return 0; // Immediate match
    
      // Compute longest suffix-prefix table
      var lsp = [0]; // Base case
      for (var i = 1; i < pattern.length; i++) {
        var j = lsp[i - 1]; // Start by assuming we're extending the previous LSP
        while (j > 0 && pattern[i] !== pattern[j])
          j = lsp[j - 1];
        if (pattern[i] === pattern[j])
          j++;
        lsp.push(j);
      }
    
      // Walk through text string
      var j = 0; // Number of chars matched in pattern
      for (var i = 0; i < text.length; i++) {
        while (j > 0 && text[i] != pattern[j])
          j = lsp[j - 1]; // Fall back in the pattern
        if (text[i]  == pattern[j]) {
          j++; // Next char matched, increment position
          if (j == pattern.length)
            return i - (j - 1);
        }
      }
      return -1; // Not found
    }
    
    console.log(kmpSearch('ays', 'haystack') != -1) // true
    console.log(kmpSearch('asdf', 'haystack') != -1) // false

    4

    • 7

      Not questioning anything on this approach… but why implementing KMP where there’s a includes or indexOf on the table. (Although the underneath impl of those maybe using KMP… not sure)

      – sphoenix

      Jul 13, 2021 at 17:00

    • 1

      KMP provides linear O(n) performance here.

      – wz366

      Jul 15, 2021 at 17:20


    • @wz366 KMP provides O(n), what about the rest? Any Idea?

      – TheLebDev

      Jul 18, 2021 at 8:12

    • If this is used for speed, it would likely run faster if you replaced .charAt(i) with [i] to avoid the extra function calls.

      – dandavis

      Aug 20, 2021 at 2:54