自动完成的正则表达式

时间:2014-09-18 15:19:17

标签: regex

我有一个短语列表,其中包含可以用空格,连字符,camelCase或PascalCase进行分类的单词。我希望能够通过只键入每个单词的几个字母来过滤这些短语,并且可能会跳过一些单词。 对于那些熟悉JetBrains IDE的人来说,它与代码完成工作方式类似。我想模拟这种行为是为了通用,而不仅仅是为了在IDE中编写代码。 (通用目的是在网站中使用它来过滤短语,也许可以与Bash一起使用它来获取文件列表,只需键入文件的一些首字母,或者我说的一些单词)。这是一个非常方便的工具,我使用了很多(在JetBrains')!

示例
给出以下短语:

  • LoremIpsumDolor
  • sitAmetConsectetur
  • adipisicing-elit-sed
  • Do Eiusmod Tempor incididunt

以下是我想要过滤的一些典型字词:

  • lodo - >返回LoremIpsumDolor(注意这里不区分大小写,这就是我想要的方式)
  • dotemporinc - >返回Do Eiusmod Tempor incididunt
  • Do - >返回LoremIpsumDolorDo Eiusmod Tempor incididunt
  • ac - >返回sitAmetConsectetur

我一直在考虑如何实现这种功能,而我能想到的最好的方法是输入要过滤的单词 - 比如示例1中的lodo,将生成一个正则表达式构造从单词的字母,由一些额外的表达式分隔,形成整个正则表达式...然后它将测试列表中的每个短语与正则表达式,并返回只匹配的那些。

我想过可能会将单词(lodo)拆分成字母,并在每个字母之间(最开头和最后)放置以下正则表达式:([a-zA-Z][a-z]*)*,(此解决方案,如果它可以工作,假设所有短语都是camel \ PascalCased,但完整的解决方案必须包括其他情况)。这将导致以下正则表达式: ^([a-zA-Z][a-z]*)*[lL]([a-zA-Z][a-z]*)*[oO]([a-zA-Z][a-z]*)*[dD]([a-zA-Z][a-z]*)*[oO]([a-zA-Z][a-z]*)*$

显然,这有一些来自回溯的巨大缺陷,更具体地说,我认为(但是肯定不确定),如果我可以禁用([a-zA-Z][a-z]*)*中内部星形表达的回溯,只保留外星的回溯,它应该工作。

我希望我能够很好地解释自己。也许这个问题有一个已知的解决方案,然后我很乐意听到它。

2 个答案:

答案 0 :(得分:4)

在考虑了几个小时之后,我使用正则表达式制作了一个解决方案,我真的认为这是一个非常合适的问题解决方案,而且它真的不那么难。

我的解决方案目前仅处理camelCase和PascalCase短语(即它只能正确过滤使用camelCase或PascalCase编写的短语),但适应其他情况应该很容易。目前,这些案例已经足够好了。

所以,这就是我想出的:

从上面的示例中提到lodo,我们应该意识到,对于给定单词中的每个字母(l o d o ),它可以是单词的第一个字母(表示它应该与大写字母匹配,或者如果它是第一个单词,它也可能是小写),或者它是单词中的下一个字母我们之前找到的(意味着它是小写的,在我们找到前一个字母之后,应该尝试匹配)。 我们还应该考虑正则表达式的行为,确切地说,是子表达式的evalutaion顺序。我们将使用以下事实:在表达式(|)中,首先尝试左侧,并且在e*?(javascript)形式的表达式中,它会找到最小的可能匹配(而不是省略问号,在这种情况下它将消耗尽可能大的字符,然后我们可能会进入回溯状态,这对我们来说是不好的。)

所以,让我们构建正则表达式。对于每个字符 c ,我们构造:

  • 如果 c 是我们的第一个字母(lodo表示l),那么:

    • c 匹配第一个单词的第一个字母,它可以是小写,我们构造:(^c)
    • 否则它必须是其他单词的第一个字母,并且必须为大写,我们构造:C
    • 我们对第一个字母的表达:(^c|C)
  • 否则:

    • 我们想首先测试我们的信是否是我们已经找到的一个词的延续。同样,我们的 c 字母(在lodo中此参数对o d o中的任何一个有效)在这种情况下必须为小写,我们构建(c)
    • 否则, c 必须是新单词中的第一个字母,这意味着它必须是大写的,我们还必须考虑我们为以前的字母构造的正则表达式,所以我们必须使用我们目前所处的整个单词,然后尝试使用其他单词,但我们优先考虑我们的大写c字母(希望这个解释很明确)。对于所有情况,我们构建了[a-z]*([A-Z][a-z]*)*?C。 ([a-z]*用于消费当前单词的剩余字母,([A-Z][a-z]*)*?用于尝试使用其他单词,如果C不是我们下一个单词的第一个字母(请记住它可以是前面2个字的下一个字母,所以...这是我的要求))
    • 我们对任何非首字母的表达式:(c|([a-z]*([A-Z][a-z]*)*?C))

因此,通过这些说明,我们可以为我们心爱的lodo构建正则表达式,这就是我们应该得到的:(^l|L)(o|([a-z]*([A-Z][a-z]*)*?O))(d|([a-z]*([A-Z][a-z]*)*?D))(o|([a-z]*([A-Z][a-z]*)*?O))

我在AngularJS项目中用一些单词对它进行了测试,看起来效果很好。我会改进它以考虑其他情况,但我认为它不应该很难。

<强>更新

稍微玩一下,我调整它以考虑我认为大多数可能的单词分离检测案例(通过camelCase,PascalCase,空格,连字符,下划线,实际上任何不是字母表的分隔符)字符)。这使得正则表达式更简洁,甚至可能更高效。我删除了我在原始答案中解释的大部分麻烦,并将所有[a-z]*([A-Z][a-z]*)*?子表达式替换为仅.*?,这是有效的,因为它没有消耗字符,直到它没有选择,这是更好的方法是先消耗字符然后回溯。

对于每个字符 c ,我们现在构造表达式:(c|.*?(C|[^a-zA-Z]c))。然而,这可能会或可能不会引入一点回溯(取决于引擎的优化 - 如果它从正则表达式构造自动机,并且如果它最小化它),在下一个字符是非字母表的情况下,并且在它不是所需的小写字母之后的下一个字符,然后它将从[^a-zA-Z]c表达式回溯到.*?表达式,然后消耗(再次)非字母字符(第一个),并继续..(这意味着,在这种情况下,我们可能会在[^a-zA-Z].*?中使用该字符两次,但如果引擎优化自动机,情况可能并非如此。 / p>

现在构建的lodo表达式为:
^(l|.*?(L|[^a-zA-Z]l))(o|.*?(O|[^a-zA-Z]o))(d|.*?(D|[^a-zA-Z]d))(o|.*?(O|[^a-zA-Z]o))

我知道我的问题没有得到普及,但我正在编写我想出的解决方案以供将来参考(即使它仅适用于我)。

答案 1 :(得分:0)

这是Javascript中的解决方案,与尝试使用自动完成功能相比,它非常简单。

const searchData = searchText => {
  const regex = new RegExp(searchText, 'gi');
  return new Promise(resolve => resolve(topMovies.filter(m => m.title.match(regex))))
};

const topMovies = [{title: "The Shawshank Redemption (1994)", rating: 9.2   },{title: "The Godfather (1972)", rating: 9.2   },{title: "The Godfather: Part II (1974)", rating: 9.0  },{title: "The Dark Knight (2008)", rating: 9.0 },{title: "12 Angry Men (1957)", rating: 8.9    },{title: "Schindler's List (1993)", rating: 8.9    },{title: "The Lord of the Rings: The Return of the King (2003)", rating: 8.9   },{title: "Pulp Fiction (1994)", rating: 8.9    },{title: "The Good, the Bad and the Ugly (1966)", rating: 8.8  },{title: "Fight Club (1999)", rating: 8.8  },{title: "The Lord of the Rings: The Fellowship of the Ring (2001)", rating: 8.8   },{title: "Forrest Gump (1994)", rating: 8.7    },{title: "Star Wars: Episode V - The Empire Strikes Back (1980)", rating: 8.7  },{title: "Inception (2010)", rating: 8.7   },{title: "The Lord of the Rings: The Two Towers (2002)", rating: 8.7   },{title: "One Flew Over the Cuckoo's Nest (1975)", rating: 8.7 },{title: "Goodfellas (1990)", rating: 8.7  },{title: "The Matrix (1999)", rating: 8.6  },{title: "Seven Samurai (1954)", rating: 8.6   },{title: "City of God (2002)", rating: 8.6 },{title: "Star Wars: Episode IV - A New Hope (1977)", rating: 8.6  },{title: "Se7en (1995)", rating: 8.6   },{title: "The Silence of the Lambs (1991)", rating: 8.6    },{title: "It's a Wonderful Life (1946)", rating: 8.6   },{title: "Life Is Beautiful (1997)", rating: 8.6   },{title: "The Usual Suspects (1995)", rating: 8.5  },{title: "Spirited Away (2001)", rating: 8.5   },{title: "Saving Private Ryan (1998)", rating: 8.5 },{title: "Léon: The Professional (1994)", rating: 8.5  },{title: "Avengers: Infinity War (2018)", rating: 8.5  },{title: "The Green Mile (1999)", rating: 8.5  },{title: "Interstellar (2014)", rating: 8.5    },{title: "American History X (1998)", rating: 8.5  },{title: "Psycho (1960)", rating: 8.5  },{title: "City Lights (1931)", rating: 8.5 },{title: "Once Upon a Time in the West (1968)", rating: 8.5    },{title: "Casablanca (1942)", rating: 8.5  },{title: "Modern Times (1936)", rating: 8.5    },{title: "The Intouchables (2011)", rating: 8.5    },{title: "The Pianist (2002)", rating: 8.5 },{title: "The Departed (2006)", rating: 8.5    },{title: "Terminator 2 (1991)", rating: 8.5    },{title: "Back to the Future (1985)", rating: 8.5  },{title: "Rear Window (1954)", rating: 8.5 },{title: "Raiders of the Lost Ark (1981)", rating: 8.5 },{title: "Whiplash (2014)", rating: 8.5    },{title: "Gladiator (2000)", rating: 8.5   },{title: "The Lion King (1994)", rating: 8.5   },{title: "The Prestige (2006)", rating: 8.5    },{title: "Memento (2000)", rating: 8.4 }];

const searchInputElement = document.querySelector('.search-input');
    const resultsElement = document.querySelector('.results');

    // Convert search results into UI suggestions
    function showSearchResults(searchQuery) {
        searchData(searchQuery).then(results => {
            const html = results.map(movie => `
      <li>
        <span class="title">${movie.title}</span>
        <span class="rating">${movie.rating}</span>
      </li>
    `);

            resultsElement.innerHTML = html.join('');
        });
    }

    // Pass 
    function handleChange() {
        return showSearchResults(this.value);
    }

    // Register for both events
    searchInputElement.addEventListener('change', handleChange);
    searchInputElement.addEventListener('keyup', handleChange);


//HTML
<form class="search-form">
    <input type="text" class="search-input" placeholder="Start typing a movie title...">
    <ul class="results"></ul>
</form>