比正则表达式更快的方式匹配字符串之间的字符?

时间:2014-12-06 23:57:41

标签: javascript regex performance

用例是我想将查询字符串与单词数组进行比较,然后返回匹配项。匹配是当一个单词包含查询字符串中的所有字符时,顺序无关紧要,重复的字符是可以的。正则表达式看起来可能太强大了(一个只需要锤子的大锤)。我编写了一个解决方案,通过循环遍历它们并使用indexOf来比较字符,但它似乎一直较慢。 (http://jsperf.com/indexof-vs-regex-inside-a-loop/10)Regex是此类操作的最快选择吗?有没有办法让我的备用解决方案更快?

 var query = "word",
    words = ['word', 'wwoorrddss', 'words', 'argument', 'sass', 'sword', 'carp', 'drowns'],
    reStoredMatches = [],
    indexOfMatches = [];

  function match(word, query) {
    var len = word.length,
      charMatches = [],
      charMatch,
      char;

    while (len--) {
      char = word[len];
      charMatch = query.indexOf(char);

      if (charMatch !== -1) {
        charMatches.push(char);
      }
    }
    return charMatches.length === query.length;
  }

  function linearIndexOf(words, query) {
    var wordsLen = words.length,
      wordMatch,
      word;

    while (wordsLen--) {
      word = words[wordsLen];

      wordMatch = match(word, query);

      if (wordMatch) {

        indexOfMatches.push(word);
      }
    }
  }

  function linearRegexStored(words, query) {
    var wordsLen = words.length,
      re = new RegExp('[' + query + ']', 'g'),
      match,
      word;

    while (wordsLen--) {
      word = words[wordsLen];
      match = word.match(re);

      if (match !== null) {

        if (match.length >= query.length) {

          reStoredMatches.push(word);
        }
      }
    }
  }

3 个答案:

答案 0 :(得分:3)

请注意,你的正则表达式是错误的,这就是为什么如此快的原因。

现在,如果您的查询是“word”(如您的示例中所示),则正则表达式将是:

/[word]/g

这意味着要查找其中一个字符:'w','o','r'或'd'。如果匹配,则match()返回true。完成。绝对比最肯定更正确的indexOf()快得多。 (即在简单匹配()调用的情况下,忽略'g'标志,因为如果任何一个匹配,则该函数返回true。)

另外,你提到了任意数量字符的想法/概念,我想如下所示:

'word', 'wwoorrddss'

如果你真的对每个角色都说“任意数字”,那么indexOf()肯定不能正确捕捉到它。因为你应该匹配无数个案例。像这样的正则表达式:

/w+o+r+d+s+/g

您肯定很难在纯JavaScript 中编写正确的代码,而不是使用正则表达式。但是,无论哪种方式,这都会有点慢。


从下面的评论中,这个单词的所有字母都是必需的,为了做到这一点,你必须有3个!测试(3阶乘)为3个字母的单词:

/(a.*b.*c)|(a.*c.*b)|(b.*a.*c)|(b.*c.*a)|(c.*a.*b)|(c.*b.*a)/

显然,一个因子会很快增加你的可能性,并在一个超长的正则表达式中吹走你的记忆(虽然你可以简化一个单词有多次相同的字母,你不必测试那个字母不止一次)。

1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
...

这可能就是为什么你在普通JavaScript中正确编写的测试更慢。

另外,在你的情况下,你应该写几乎像拼字游戏词典中所做的那样:所有字母按字母顺序排列一次(拼字游戏保持重复)。所以“word”这个词就是“dorw”。正如你在你的例子中所示,“wwoorrddss”这个词将是“dorsw”。您可以使用一些后端工具来生成您的单词表(因此您仍然将它们写为“单词”和“单词”,并且您的工具按下它们并将它们转换为“dorw”和“dorsw”。)然后您可以对您按字母顺序测试的单词的字母,结果是您不需要正则表达式的愚蠢因子,您可以简单地执行此操作:

/d.*o.*r.*w/

这将匹配任何包含单词“word”的单词,例如“password”。

对字母进行排序的一种简单方法是将单词分成一个字母数组,然后对数组进行排序。您可能仍会获得重复项,这取决于排序功能。 (我不认为默认的JavaScript排序会自动删除重复项。)


还有一个细节,如果你测试的应该是不区分大小写的,那么你想在运行测试之前将你的字符串转换为小写。如下所示:

query = query.toLowerCase();

早期在您的顶级功能中。

答案 1 :(得分:0)

您正在尝试加速算法“字中的字符是查询字符的子集。”您可以将此检查短路并避免一些分配(更具可读性但不严格需要)。请尝试以下匹配版本

  function match(word, query) {
    var len = word.length;
    while (len--) {
      if (query.indexOf(word[len]) === -1) { // found a missing char
        return false;
      }
    }
    return true; // couldn't find any missing chars
  }

This gives a 4-5X improvement

根据应用程序的不同,您可以尝试预先分配单词并将单词中的每个单词预先分类为另一种优化。

答案 2 :(得分:0)

正则表达式匹配算法构造有限状态自动机,并根据当前状态和字符从左到右读取 。这涉及阅读每个角色一次并做出决定。

对于静态字符串(在几个文本上查看固定字符串),您有更好的算法,例如Knuth-Morris,允许您一次比一个字符更快,但您必须了解此算法不是用于匹配正则表达式,只是简单的字符串。

如果你对Knuth-Morris感兴趣(还有其他一些算法),只需在维基百科中进行一轮。 http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm

你可以做的一件好事就是调查你是否正在使用DFA或NDFA进行匹配例程,因为NDFA占用的内存更少,更容易计算,但是DFA做得更快,但是有一些编译惩罚等等记忆占用。

Knuth-Morris算法还需要在工作之前将字符串编译成自动机,因此如果您只是在某个字符串中找到一个单词,它可能不适用于您的问题。