Java:如何实现通配符匹配?

时间:2016-12-13 00:50:28

标签: java algorithm wildcard

我正在研究如何在BST中找到最接近目标的k值,并且通过规则遇到以下实现:

  

'?'匹配任何单个字符。

     

'*'匹配任何字符序列(包括空序列)。

     

匹配应覆盖整个输入字符串(非部分)。

     

函数原型应该是:   bool isMatch(const char * s,const char * p)

     

一些例子:

     

isMatch(“aa”,“a”)→false

     

isMatch(“aa”,“aa”)→true

     

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));
    }
}

我的问题是,有两个额外索引iijj的原因是什么,为什么它们会被-1初始化?每个人的目的是什么?不会使用ij遍历它吗?

第一个if案例中ii=i;jj=j;的目的是什么,第三个案例中i=ii+1;j=jj;是什么?

最后,在什么情况下你会遇到while(j<p.length() && p.charAt(j)=='*') j++;

示例对理解非常有帮助。 提前感谢您,并接受回答/投票。

4 个答案:

答案 0 :(得分:0)

看起来iijj用于处理与任何序列匹配的通配符“*”。它们的初始化为-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")sp完全相同,因此程序重复评估else-if块没有问题,最后在i等于2(长度为“ AA“)。第二个while循环从不运行,因为j不小于p.length() - 实际上,因为else-if一起递增ij,它们都等于2和2不小于“aa”的长度。我们返回j == p.length(),其评估结果为2 == 2,并获得true

isMatch("aaa","aa"):这个失败的原因与第一个相同。也就是说,字符串的长度不一样,我们从不打过通配符。

isMatch("aa","*"):这就是它变得有趣的地方。首先我们将输入if块,因为我们在p中看到了“*”。我们将iijj设置为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(),询问我们是否检查了整个模式。通常我们会停在第一个通配符,因为它匹配任何东西,所以我们需要最终遍历模式的其余部分,并确保它只包含通配符。

从这里你可以找出其他测试用例背后的逻辑。我希望这有帮助!

答案 1 :(得分:0)

让我们看一下这个故障。

首先,这是字符串(s)和通配符模式(p)的并行迭代,使用变量i来索引s和变量{{ 1}}索引j

p循环将在到达while结束时停止迭代。当发生这种情况时,希望已经达到s的结尾,在这种情况下,它会返回ptrue)。

但是,如果j==p.length()p结尾,那也是有效的(例如*),以及isMatch("ab", "ab*")循环确保的内容,即任何此时模式中的while(j<p.length() && p.charAt(j)=='*') j++;被跳过,如果到达*的末尾,则返回p。如果未到达true的结尾,则返回false。

这是你上一个问题的答案。现在让我们来看看循环。只要存在匹配项,p将迭代else ifi,例如j'a' == 'a'

当找到'a' == '?'通配符时(第一个*),它会保存ifii中的当前位置,以防需要回溯,然后跳过通配符字符。

这基本上首先假设通配符匹配空字符串(例如jj)。当它继续迭代时,isMatch("ab", "a*b")将匹配其余部分,方法最终返回else if

现在,如果找到不匹配(true块),它将尝试回溯。当然,如果它没有保存的通配符(else),它就不能回溯,所以它只返回jj==-1。这就是false初始化为jj的原因,因此它可以检测是否保存了通配符。 -1可以初始化为任何内容,但会初始化为ii以保持一致性。

如果在-1ii中保存了通配符位置,它将恢复这些值,然后将jj转发一个,即假设下一个字符与通配符匹配,匹配的其余部分将成功并返回i

这就是逻辑。现在,它可以进行一点点优化,因为回溯是次优的。它目前将true重置为j,将*重置为下一个字符。当它循环时,它将进入i并再次在if中保存保存值并将jj值保存在i中,然后递增ii 。由于这是给定的(除非达到j的结束),回溯也可以这样做,保存迭代循环,即

s

答案 2 :(得分:0)

代码看起来很麻烦。 (见下文)

iijj的表面目的是实现一种回溯形式。

例如,当您尝试匹配&#34; abcde&#34;对于模式&#34; a * e&#34;,算法将首先匹配&#34; a&#34;在反对&#34; a&#34;的模式中在输入字符串中。然后它将急切地匹配&#34; *&#34;反对其余的字符串...并发现它犯了一个错误。此时,它需要回溯并尝试替代

iijj用于记录要回溯的点,并且使用这些变量要么记录新的回溯点,要么回溯。

或者至少,这可能是作者在某些方面的意图。

while(j<p.length() && p.charAt(j)=='*') j++;似乎正在处理边缘案例

但是,我不认为这段代码是正确的。

  1. 在有多个&#34; *&#34;的情况下,它确实无法应对回溯。模式中的通配符。这需要一个递归的解决方案。

  2. 部分:

        if(j<p.length() && p.charAt(j)=='*') {
            ii=i;
            jj=j;
            j++;
    

    没有多大意义。我认为它应该增加i而不是j。它可能会&#34;网格&#34;与else部分的行为有关,但即使这样做,这也是一种令人费解的编码方式。

  3. 建议:

    1. 不要使用此代码作为示例。即使它有效(在有限的意义上),它也不是一个很好的方式来完成这项任务,或者是一个清晰或良好风格的例子。
    2. 我会通过将通配符模式转换为正则表达式,然后使用Pattern / Matcher进行匹配来处理此问题。

      例如:Wildcard matching in Java

答案 3 :(得分:0)

我知道您在问有关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进行检查,...)。