正则表达式通配符匹配

32
我有一个包含大约12万个英语单词的列表(基本上是语言中的所有单词)。
我需要一个正则表达式,允许使用通配符字符,即 * ?搜索这些单词。
一些例子:
- 如果用户搜索m?st*,它将匹配例如mastermistermistery。 - 如果用户搜索*ind(任何以ind结尾的单词),它将匹配windbindblindgrind
现在,大多数用户(特别是那些不熟悉正则表达式的用户)知道是替换正好1个字符,而*是替换0、1或多个字符。我绝对希望基于此构建我的搜索功能。
我的问题是:如何将用户输入的内容(例如m?st*)转换为正则表达式?
我搜索了网络(显然包括本网站),但我找到的都是试图教我太多东西的教程或类似的问题,但无法提供答案。
我能想到的就是必须用 . 替换。因此,m?st*变成了m.st*。但我不知道用什么替换*
非常感谢您的任何帮助。谢谢。
PS:我完全是新手正则表达式。我知道它们可以有多强大,但我也知道它们很难学习。所以我从来没有花时间去做...

2
请记住,查询中出现的任何其他正则表达式字符也必须进行转义。如果有人输入了 ^\w..,您可能不想以原始形式将其传递到正则表达式引擎。 - Gareth
@SoboLAN:你能否分享单词集合?我需要它来开发我的需求词典。 - Hussain Akhtar Wahid 'Ghouri'
@HussainAkhtarWahid 我是从另一个程序的数据库中获取它们的,我不记得链接了,而且我浏览器历史记录中也没有了。但是,我在这里上传了它们:http://www.2shared.com/file/elLSFPDx/dictionarywords.html 。文件中的每一行代表一个单词。格式如下:word|definition1;definition2;definition3。因此,分隔符为|;。注意:定义可以有任意数量(1、2、3等)。希望这可以帮到你。祝你好运。 - Radu Murzea
似乎足够了,谢谢您的快速和充分回复。 - Hussain Akhtar Wahid 'Ghouri'
9个回答

27

如果你想避免一些有趣的行为,我建议你使用\w而不是.

.匹配空格和其他非单词符号,这可能不是你想要的。

所以我会用\w替换?,用\w*替换*

另外,如果你想让*至少匹配一个字符,请将其替换为\w+。这意味着ben*将匹配bendbending,但不匹配ben - 这取决于你的要求。


问题说:“当*代替0、1或多个字符时” - Gareth
2
@Gareth,是的,我看到了。只是想提供额外的信息。 - gnomed
@gnomedдёәд»Җд№Ҳ\wжҜ”.жӣҙеҘҪпјҹ - Radu Murzea
1
@SoboLAN 在你评论的时候正在编辑我的答案 :). 请看上面的修改,但基本上 . 匹配空格,我认为这不是你想要的。 - gnomed
如果在\w*后面没有加上?符号,使其变成\w*?,那么表达式就会变得贪婪,这可能不是他想要的,而且至少会极大地减慢搜索速度。 - Dan W
那个评论可能需要一些详细说明。根据描述和用户非技术背景的事实,它听起来像是OP想要基于贪婪行为。他们甚至不会理解贪婪与非贪婪。听起来他们期望te*g匹配以te开头并以g结尾的整个单词,而te\w*?g也匹配任何以te开头且在其中有一个g的单词,这可能是OP所描述的用例中意外的行为(如果用户想要这样做,他们会说te*g*)。可以使用锚点,但那基本上就是贪婪了。 - gnomed

9

请看这个库:https://github.com/alenon/JWildcard

它通过正则表达式引号包装所有非通配符特定部分,因此不需要进行特殊字符处理: 这个通配符:

"mywil?card*"

将被转换为以下正则表达式字符串:
"\Qmywil\E.\Qcard\E.*"

如果你想将通配符转换成正则表达式字符串,请使用以下方法:
JWildcard.wildcardToRegex("mywil?card*");

如果您想直接检查匹配,可以使用以下内容:
JWildcard.matches("mywild*", "mywildcard");

默认通配符规则为"?" -> ".", "<em>" -> ".",但如果您愿意,可以通过定义新规则来更改默认行为。

JWildcard.wildcardToRegex(wildcard, rules, strict);

您可以使用源代码或从Bintray JCenter直接使用maven或gradle进行下载:https://bintray.com/yevdo/jwildcard/jwildcard Gradle方式:
compile 'com.yevdo:jwildcard:1.4'

Maven方式:

<dependency>
  <groupId>com.yevdo</groupId>
  <artifactId>jwildcard</artifactId>
  <version>1.4</version>
</dependency>

3
非常感谢您的库没有包含任何传递依赖项! - omerkarj

8

?替换为.,将*替换为.*


没有在 .* 后面加上 ? 符号变成 .*?,表达式就会变得贪婪,这至少会极大地减慢搜索速度。 - Dan W
你能给出完整的语句吗? - n8jadams

6
以下是将通配符转换为正则表达式的方法:
  1. 在所有特殊字符 ([{\^-=$!|]}).+ 前面加上 \,这样它们就会被视为字符而不会让用户体验出现意外。此外,您可以将其括在\Q(引用开始)和\E(引用结束)之间。有关安全性的段落也请参见。
  2. \S*替换*通配符。
  3. \S?替换?通配符。
  4. 可选:在模式前面添加^ - 这将强制与开头完全匹配。
  5. 可选:在模式后面追加$ - 这将强制与结尾完全匹配。

    \S代表零个或多个非空格字符。

如果在*或+后面有要匹配的字符,请考虑使用勉强的(非贪婪的)量词。这可以通过在*或+后面添加?来实现,如下所示:\S*?\S*+?

请考虑安全性:用户将向您发送要运行的代码(因为正则表达式也是一种代码,而用户字符串被用作正则表达式)。您应避免将未转义的正则表达式传递给应用程序的任何其他部分,并仅用于过滤通过其他方式检索的数据。因为如果这样做,用户可以通过提供不同的通配符字符串中的不同正则表达式来影响您的代码速度 - 这可能会用于DoS攻击。

以下是显示类似模式执行速度的示例:

seq 1 50000000 > ~/1
du -sh ~/1
563M
time grep -P '.*' ~/1 &>/dev/null
6.65s
time grep -P '.*.*.*.*.*.*.*.*' ~/1 &>/dev/null
12.55s
time grep -P '.*..*..*..*..*.*' ~/1 &>/dev/null
31.14s
time grep -P '\S*.\S*.\S*.\S*.\S*\S*' ~/1 &>/dev/null
31.27s

我建议不要使用 .*,因为它可以匹配任何内容,通常各个内容之间是用空格分隔的。

3
  1. 将所有'?'字符替换为'\w'
  2. 将所有'*'字符替换为'\w*'

*运算符重复前面的项'.'(任何字符) 0次或更多次。

这假定单词中没有包含'.','*'和'?'。

这是一个很好的参考资料。

http://www.regular-expressions.info/reference.html


2

. 是一个表达式,匹配任何一个字符,正如你所发现的。在你漫长的搜索过程中,你无疑也会遇到 *,它是一个重复运算符,在表达式后使用时,可以匹配“前面的表达式零次或多次连续出现”。

因此,与你对 * 的意思相当的是将这两个结合起来:.*。这就意味着“任何字符零次或多次出现”。

请参阅重复运算符的正则表达式教程


是的,我知道,我在网上找东西不太擅长,尤其是对它们完全陌生的情况下 :)。 - Radu Murzea

2

*替换为.*(正则表达式中表示“0个或多个任意字符”的等价形式)。


0

这是我使用的:

String wildcardToRegex(String wildcardString) {
    // The 12 is arbitrary, you may adjust it to fit your needs depending
    // on how many special characters you expect in a single pattern.
    StringBuilder sb = new StringBuilder(wildcardString.length() + 12);
    sb.append('^');
    for (int i = 0; i < wildcardString.length(); ++i) {
        char c = wildcardString.charAt(i);
        if (c == '*') {
            sb.append(".*");
        } else if (c == '?') {
            sb.append('.');
        } else if ("\\.[]{}()+-^$|".indexOf(c) >= 0) {
            sb.append('\\');
            sb.append(c);
        } else {
            sb.append(c);
        }
    }
    sb.append('$');
    return sb.toString();
}

来自https://dev59.com/imYq5IYBdhLWcg3w30Tc#26228852的特殊字符列表。


0
function matchWild(wild,name)
{
    if (wild == '*') return true;

    wild = wild.replace(/\./g,'\\.');
    wild = wild.replace(/\?/g,'.');
    wild = wild.replace(/\\/g,'\\\\');  
    wild = wild.replace(/\//g,'\\/');
    wild = wild.replace(/\*/g,'(.+?)');

    var re = new RegExp(wild,'i');
    return re.test(name);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接