[英]Java: How to implement wildcard matching?
我正在研究如何在 BST 中找到最接近目标的 k 值,并且遇到了以下带有规则的实现:
'? 匹配任何单个字符。
'*' 匹配任何字符序列(包括空序列)。
匹配应该覆盖整个输入字符串(不是部分)。
函数原型应该是: bool isMatch(const char *s, const char *p)
一些例子:
isMatch("aa","a") → false
isMatch("aa","aa") → 真
isMatch("aaa","aa") → false
isMatch("aa", "*") → true
isMatch("aa", "a*") → true
isMatch("ab", "?*") → true
isMatch("aab", "c a b") → false
代码:
import java.util.*;
public class WildcardMatching {
boolean isMatch(String s, String p) {
int i=0, j=0;
int ii=-1, jj=-1;
while(i<s.length()) {
if(j<p.length() && p.charAt(j)=='*') {
ii=i;
jj=j;
j++;
} else if(j<p.length() &&
(s.charAt(i) == p.charAt(j) ||
p.charAt(j) == '?')) {
i++;
j++;
} else {
if(jj==-1) return false;
j=jj;
i=ii+1;
}
}
while(j<p.length() && p.charAt(j)=='*') j++;
return j==p.length();
}
public static void main(String args[]) {
String s = "aab";
String p = "a*";
WildcardMatching wcm = new WildcardMatching();
System.out.println(wcm.isMatch(s, p));
}
}
我的问题是,有两个额外的索引ii
和jj
的原因是什么,为什么它们被初始化为-1
? 每个的目的是什么? 用i
和j
遍历它还不够吗?
ii=i;
的目的是什么ii=i;
并且jj=j;
在第一种情况下, i=ii+1;
并且j=jj;
在第三种情况下?
最后,在什么情况下你会遇到while(j<p.length() && p.charAt(j)=='*') j++;
?
例子对理解非常有帮助。 在此先感谢您,并将接受回答/投票。
看起来ii
和jj
用于处理通配符“*”,它匹配任何序列。 它们对 -1 的初始化充当一个标志:它告诉我们是否遇到了不匹配的序列并且当前没有评估“*”。 我们可以一次一个地浏览您的示例。
请注意, i
与参数s
(原始字符串)相关,而j
与参数p
(模式)相关。
isMatch("aa","a")
:这将返回 false 因为j<p.length()
语句将在我们离开 while 循环之前失败,因为p
("a") 的长度仅为 1 而长度为s
("aa") 是 2,所以我们将跳转到 else 块。 这就是 -1 初始化的用武之地:因为我们从未在p
看到任何通配符,所以jj
仍然是 -1,表明字符串无法匹配,因此我们返回 false。
isMatch("aa","aa")
: s
和p
完全一样,所以程序反复评估 else-if 块没有问题,一旦i
等于 2(“aa 的长度”),最终跳出 while 循环”)。 第二个while循环永远不会运行,因为j
不小于p.length()
——事实上,由于else-if将i
和j
在一起,它们都等于2,而且2不小于“的长度”啊”。 我们返回j == p.length()
,其计算结果为2 == 2
,并得到true
。
isMatch("aaa","aa")
:这个失败的原因与第一个相同。 也就是说,字符串的长度不同,我们从未遇到通配符。
isMatch("aa","*")
:这就是有趣的地方。 首先我们将进入 if 块,因为我们在p
看到了一个“*”。 我们将ii
和jj
设置为 0 并仅增加j
。 在第二次迭代中, j<p.length()
失败,所以我们跳转到 else 块。 jj
不再是 -1(它是 0),所以我们将j
重置为 0 并将i
设置为 0+1。 这基本上允许我们继续评估通配符,因为j
只是被重置为jj
,它保存了通配符的位置,而ii
告诉我们从原始字符串中的哪里开始。 这个测试用例还解释了第二个 while 循环。 在某些情况下,我们的模式可能比原始字符串短得多,因此我们需要确保它与通配符匹配。 例如, isMatch("aaaaaa","a**")
应该返回 true,但最终的 return 语句是检查j == p.length()
,询问我们是否检查了整个模式。 通常我们会在第一个通配符处停止,因为它匹配任何东西,所以我们最终需要遍历模式的其余部分并确保它只包含通配符。
从这里您可以找出其他测试用例背后的逻辑。 我希望这有帮助!
让我们看看这个有点乱。
首先,这是字符串 ( s
) 和通配符模式 ( p
) 的并行迭代,使用变量i
索引s
和变量j
索引p
。
当到达s
结尾时, while
循环将停止迭代。 发生这种情况时,希望也已到达p
结尾,在这种情况下,它将返回true
( j==p.length()
)。
然而,如果p
以*
结尾,那也是有效的(例如isMatch("ab", "ab*")
),这就是while(j<p.length() && p.charAt(j)=='*') j++;
循环确保,即此时模式中的任何*
被跳过,如果到达p
末尾,则返回true
。 如果未到达p
结尾,则返回 false。
那是你最后一个问题的答案。 现在让我们看看循环。 只要存在匹配, else if
就会迭代i
和j
,例如'a' == 'a'
或'a' == '?'
.
当找到*
通配符时(首先是if
),它将当前位置保存在ii
和jj
,以防需要回溯,然后跳过通配符。
这基本上从假设通配符匹配空字符串开始(例如isMatch("ab", "a*b")
)。 当它继续迭代时, else if
将匹配其余部分,并且方法最终返回true
。
现在,如果发现不匹配( else
块),它将尝试回溯。 当然,如果它没有保存的通配符( jj==-1
),它就不能回溯,所以它只返回false
。 这就是jj
被初始化为-1
的原因,因此它可以检测是否保存了通配符。 ii
可以被初始化为任何东西,但为了一致性被初始化为-1
。
如果在ii
和jj
保存了通配符位置,它将恢复这些值,然后将i
转发一个,即假设如果下一个字符与通配符匹配,则其余匹配将成功并返回true
。
这就是逻辑。 现在,它可以稍微优化一下,因为回溯是次优的。 它当前将j
重置回*
,并将i
重置回下一个字符。 当它循环时,它会进入if
并再次将保存值保存在jj
并将i
值保存在ii
,然后增加j
。 由于这是给定的(除非到达s
结尾),回溯也可以这样做,从而节省迭代循环,即
} else {
if(jj==-1) return false;
i=++ii;
j=jj+1;
}
代码在我看来有问题。 (见下文)
ii
和jj
的表面目的是实现一种形式的回溯。
例如,当您尝试将“abcde”与模式“a*e”进行匹配时,算法将首先将模式中的“a”与输入字符串中的“a”进行匹配。 然后它会急切地将“*”与字符串的其余部分进行匹配……并发现它犯了一个错误。 那时,它需要回溯并尝试替代方案
ii
和jj
用于记录要回溯的点,这些变量的用途是记录新的回溯点或回溯。
或者至少,这可能是作者在某个时候的意图。
while(j<p.length() && p.charAt(j)=='*') j++;
似乎正在处理边缘情况
但是,我认为这段代码不正确。
在模式中有多个“*”通配符的情况下,它肯定不会处理回溯。 这需要递归解决方案。
那个部分:
if(j<p.length() && p.charAt(j)=='*') { ii=i; jj=j; j++;
没有多大意义。 我原以为它应该增加i
而不是j
。 它可能与else
部分的行为“啮合”,但即使这样做也是一种复杂的编码方式。
建议:
我会通过将通配符模式转换为正则表达式,然后使用Pattern
/ Matcher
进行Matcher
来处理这个问题。
例如: Java 中的通配符匹配
我知道你在问 BST,但老实说,也有一种使用正则表达式的方法(不是用于竞争性编程,但在生产环境中使用足够稳定和快速):
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class WildCardMatcher{
public static void main(String []args){
// Test
String urlPattern = "http://*.my-webdomain.???",
urlToMatch = "http://webmail.my-webdomain.com";
WildCardMatcher wildCardMatcher = new WildCardMatcher(urlPattern);
System.out.printf("\"%s\".matches(\"%s\") -> %s%n", urlToMatch, wildCardMatcher, wildCardMatcher.matches(urlToMatch));
}
private final Pattern p;
public WildCardMatcher(final String urlPattern){
Pattern charsToEscape = Pattern.compile("([^*?]+)([*?]*)");
// here we need to escape all the strings that are not "?" or "*", and replace any "?" and "*" with ".?" and ".*"
Matcher m = charsToEscape.matcher(urlPattern);
StringBuffer sb = new StringBuffer();
String replacement, g1, g2;
while(m.find()){
g1 = m.group(1);
g2 = m.group(2);
// We first have to escape pattern (original string can contain charachters that are invalid for regex), then escaping the '\' charachters that have a special meaning for replacement strings
replacement = (g1 == null ? "" : Matcher.quoteReplacement(Pattern.quote(g1))) +
(g2 == null ? "" : g2.replaceAll("([*?])", ".$1")); // simply replacing "*" and "?"" with ".*" and ".?"
m.appendReplacement(sb, replacement);
}
m.appendTail(sb);
p = Pattern.compile(sb.toString());
}
@Override
public String toString(){
return p.toString();
}
public boolean matches(final String urlToMatch){
return p.matcher(urlToMatch).matches();
}
}
您仍然可以实现一系列优化(小写/大写区分,为要检查的字符串设置最大长度以防止攻击者让您检查 4-GigaByte-String,...)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.