'Regular Expression Wildcard Matching
I have a list of about 120 thousand english words (basically every word in the language).
I need a regular expression that would allow searching through these words using wildcards characters, a.k.a. *
and ?
.
A few examples:
- if the user searches for
m?st*
, it would match for examplemaster
ormister
ormistery
. - if the user searches for
*ind
(any word ending inind
), it would matchwind
orbind
orblind
orgrind
.
Now, most users (especially the ones who are not familiar with regular expressions) know that ?
is a replacement for exactly 1 character, while *
is a replacement for 0, 1 or more characters. I absolutely want to build my search feature based on this.
My questions is: How do I convert what the user types (m?st*
for example) to a regular expression ?
I searched the web (obviously including this website) and all I could find were tutorials that tried to teach me too much or questions that were somewhat similar, but not enough as to provide an answer to my own problem.
All I could figure out was that I have to replace ?
with .
. So m?st*
becomes m.st*
. However, I have no idea what to replace *
with.
Any help would be greatly appreciated. Thank you.
PS: I'm totally new to regular expressions. I know how powerful they can be, but I also know they can be very hard to learn. So I just never took the time do to it...
Solution 1:[1]
Unless you want some funny behaviour, I would recommend you use \w
instead of .
.
matches whitespace and other non-word symbols, which you might not want it to do.
So I would replace ?
with \w
and replace *
with \w*
Also if you want *
to match at least one character, replace it with \w+
instead. This would mean that ben*
would match bend
and bending
but not ben
- it's up to you, just depends what your requirements are.
Solution 2:[2]
Take a look at this library: https://github.com/alenon/JWildcard
It wraps all not wildcard specific parts by regex quotes, so no special chars processing needed: This wildcard:
"mywil?card*"
will be converted to this regex string:
"\Qmywil\E.\Qcard\E.*"
If you wish to convert wildcard to regex string use:
JWildcard.wildcardToRegex("mywil?card*");
If you wish to check the matching directly you can use this:
JWildcard.matches("mywild*", "mywildcard");
Default wildcard rules are "?" -> ".", "" -> ".", but you can change the default behaviour if you wish, by simply defining the new rules.
JWildcard.wildcardToRegex(wildcard, rules, strict);
You can use sources or download it directly using maven or gradle from Bintray JCenter: https://bintray.com/yevdo/jwildcard/jwildcard
Gradle way:
compile 'com.yevdo:jwildcard:1.4'
Maven way:
<dependency>
<groupId>com.yevdo</groupId>
<artifactId>jwildcard</artifactId>
<version>1.4</version>
</dependency>
Solution 3:[3]
Replace ?
with .
and *
with .*
.
Solution 4:[4]
Here is a way to transform wildcard into regex:
- Prepend all special characters ([{\^-=$!|]}).+ with \ - so they are matched as characters and don't make user experience unexpected. Also you could enclose it within \Q (which starts the quote) and \E (which ends it). Also see paragraph about security.
- Replace * wildcard with \S*
- Replace ? wildcard with \S?
- Optionally: prepend pattern with ^ - this will enforce exact match with the beginning.
Optionally: append $ to pattern - this will enforce exact match with the end.
\S - stand for non-space character, which happens zero or more times.
Consider using reluctant (non-greedy) quantifiers if you have characters to match after * or +. This can be done by adding ? after * or + like this: \S*? and \S*+?
Consider security: user will send you code to run (because regex is kind of a code too, and user string is used as the regex). You should avoid passing unescaped regex to any other parts of application and only use to filter data retrieved by other means. Because if you do user can affect speed of your code by supplying different regex withing wildcard string - this could be used in DoS attacks.
Example to show execution speeds of similar patterns:
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
I'd suggest against using .* simply because it can match anything, and usually things are separated with spaces.
Solution 5:[5]
- Replace all '?' characters with '\w'
- Replace all '*' characters with '\w*'
The '*' operator repeats the previous item '.' (any character) 0 or more times.
This assumes that none of the words contain '.', '*', and '?'.
This is a good reference
Solution 6:[6]
.
is an expression that matches any one character, as you've discovered. In your hours of searching, you undoubtedly also stumbled across *
, which is a repetition operator that when used after an expression matches the preceding expression zero or more times in a row.
So the equivalent to your meaning of *
is putting these two together: .*
. This then means "any character zero or more times".
Solution 7:[7]
Replace *
with .*
(the regex equivalent of "0 or more of any character").
Solution 8:[8]
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);
}
Solution 9:[9]
This is what I use:
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();
}
Special character list from https://stackoverflow.com/a/26228852/1808989.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Lu55 |
Solution 3 | NPE |
Solution 4 | |
Solution 5 | Mark Sholund |
Solution 6 | |
Solution 7 | Amber |
Solution 8 | Shell |
Solution 9 | Community |