将字符串与通配符模式匹配的递归函数

Posted

技术标签:

【中文标题】将字符串与通配符模式匹配的递归函数【英文标题】:Recursive function to match a string against a wildcard pattern 【发布时间】:2011-02-28 11:25:55 【问题描述】:

所以我一整天都在尝试解决这个任务,就是无法完成。

以下函数接受 2 个字符串,第二个(不是第一个)可能包含 *(星号)。* 是一个字符串(空,1 个字符或更多)的替换,它可以出现(仅在 s2 中)一次、两次、更多或根本不出现,它不能与另一个 * 相邻(@987654324 @),不需要检查。

public static boolean samePattern(String s1, String s2)

如果字符串的模式相同,则返回 true。 它必须是递归的,不能使用任何循环、静态和全局变量。 可以使用局部变量和方法重载。

只能使用这些方法:charAt(i)substring(i)substring(i, j)length()

示例:

1:TheExamIsEasy; 2:The*xamIs*y → 真 1:TheExamIsEasy; 2:Th*mIsEasy* → 真 1:TheExamIsEasy; 2:* → 真 1:TheExamIsEasy; 2:TheExamIsEasy → 真 1:TheExamIsEasy; 2:The*IsHard → 错误

我尝试使用 charAt 逐个比较字符,直到遇到星号,然后通过比较连续字符 (i+1) 与位置处的 s1 的字符来检查星号是否为空i,如果为真 -- 继续递归,使用 i+1 作为 s2 的计数器和 i 作为 s1 的计数器; 如果为 false -- 继续使用 i+1 作为两者的计数器进行递归。 继续此操作,直到找到另一个星号或字符串结尾。

我不知道,我的大脑失去了对事物的追踪,无法集中注意力,有任何指针/提示吗?我的方向正确吗?

另外,有人告诉我们要使用回溯技术来解决这个问题。

到目前为止我的代码(即使在理论上也不能完成这项工作):

public static boolean samePattern(String s1, String s2) 
    if (s1.equals(s2) || s2 == "*") 
        return true;
    
    return samePattern(s1, s2, 1);

public static boolean samePattern(String s1, String s2, int i)

    if (s1.equals(s2))
        return true;
    if (i == s2.length() - 1) // No *'s found -- not same pattern.
        return false;

    if (s1.substring(0, i).equals(s2.substring(0, i)))
        samePattern(s1, s2, i+1);
    else if (s2.charAt(i-1) == '*')
        samePattern(s1.substring(0, i-1), s2.substring(0, i), 1); // new smaller strings.
    else
        samePattern(s1.substring(1), s2, i);

【问题讨论】:

感谢您提出建议,而不是整个解决方案。 【参考方案1】:

这里有一些 Python “伪代码”可能会有所帮助

def samePattern(s1,s2):
    if s2 == "*" or s1 == s2: return True
    if s1 == "": return False
    if s1[0] == s2[0]: return samePattern(s1[1:], s2[1:])
    if s2[0] == "*": return samePattern(s1, s2[1:]) or samePattern(s1[1:], s2)
    return False

这里是转换代码的粗略指南

s[0] = the first character
s[1:] = the string minus the first character

【讨论】:

第一个if中的s2=="*"真的有必要吗?你不应该检查s2 是否比s1 长(例如在你的第二个if 中)吗? @strager,是的,其余代码的编写方式是必要的。 非常感谢!也谢谢大家,我现在明白了。 :)【参考方案2】:

您当前方法的问题在于它没有考虑 * 可以匹配的所有可能子字符串。例如,samePattern("ababababab", "a*b") 应该返回 true; * 可以匹配字符串的第一个和最后一个字母以外的所有字母,但您的代码假定由于后面的字母是 b,因此 * 匹配空字符串。

我建议在寻找匹配项时将 samePattern 视为“消耗”它的两个输入字符串。在每一步,samePattern 应该只需要查看每个字符串的第一个字符来决定是否匹配第一个字符,如果是,则进行递归调用以检查字符串的其余部分.诀窍是知道当您在模式字符串中到达 * 时要做什么,因为它可能用于匹配 s1 中的第一个字符,也可能不用于匹配。您不需要查看字符串的其余部分来决定要做什么。

由于这是家庭作业,我将不再详细说明发生在你身上的事情,但希望这能让你思考正确的道路。

【讨论】:

是的;使用递归来处理简化的、更简单的输入。【参考方案3】:

这是用 c# 编写的示例解决方案。很抱歉缺少 cmets,但我没有时间看它们:/ 如果你明天仍然需要它们,那么我可以写一些,但我希望你能抓住这个想法。

 public static bool CompareString(string s1, string s2, bool wildCard)
 
        // Both strings are empty
        if ((s1.Length == 0) && (s2.Length == 0)) return true;

        // Second string is empty and there is wildCard character
        if (s2.Length == 0 && wildCard) return true;

        //First string is empty. Answer will be true only if all characters in second string are *.
        if (s1.Length == 0 && s2.Length > 0 && s2[0] == '*')
        
            string newS2 = s2.Remove(0, 1);
            return CompareString(s1, newS2, true);
        

        // One of the strings is empty, and second one is not.
        if (s1.Length * s2.Length == 0) return false;

        if (wildCard)
        
            string newS1 = s1.Remove(0, 1);
            if (CompareString(newS1,s2,true) || CompareString(newS1,s2,false))
            
                return true;
            
        
        else
        
            if (s2[0] == '*')
            
                string newS2 = s2.Remove(0,1);
                if (CompareString(s1,newS2,true) || CompareString(s1,newS2,false))
                
                    return true;
                
            
            else
            
                if (s1[0] == s2[0])
                
                    string newS1 = s1.Remove(0,1);
                    string newS2 = s2.Remove(0,1);
                    return CompareString(newS1,newS2,false);
                
                else
                
                    return false;
                
            
        
        return false;
    

【讨论】:

不需要包含“通配符”标志的辅助方法 - 您可以用更少的代码做到这一点。【参考方案4】:

在处理这样的算法时,将问题分解成小块通常是值得的。

由于您正在解析字符串,因此请逐个字符地考虑解决方案。此外,由于您无法控制这些字符串的实际大小,因此请限制自己在任何给定时间只考虑字符串的第一个字符。 (嗯 - 除了一个例外)

一旦您确定要处理的字符值得进一步调查字符串的其余部分,将它们扔掉;保留它们只会增加复杂性,那何必呢? (相反,如果字符完全不匹配,你就完成了 - 对吗?)

当然,这是对字符串的递归,因此您必须有几个控制失败/成功的条件来处理字符串的整体状态 - 但这些不是问题的核心 - 检查函数顶部的字符串状态,然后继续。

我有一个算法(11 行代码,加上大括号),如果你想要一个完整的解决方案,我可以发布它 - 但我不确定你的消息是否想获得算法,或者只是指针。

【讨论】:

自我注意:不要开始输入答案,分心,然后在没有检查更新的情况下完成答案。 Paul Kuliniewicz 所做的大部分工作都是我写的。 :( 我会把它留在这里,以防我写东西的方式与 OP 产生共鸣。 这不会降低您的回答的有效性或帮助。 =]【参考方案5】:

这是我解决它的方法...

public static void main(String[] args)

    System.out.println(samePattern("TheExamIsEasy", "The*xamIs*y")); // True
    System.out.println(samePattern("TheExamIsEasy", "Th*mIsEasy*")); // True
    System.out.println(samePattern("TheExamIsEasy", "*")); // True
    System.out.println(samePattern("TheExamIsEasy", "TheExamIsEasy")); // True
    System.out.println(samePattern("TheExamIsEasy", "The*IsHard")); // false


public static boolean samePattern(String s1, String s2)

    if (s1.length() == 0 && s2.length() == 0 || 
            s1.length() == 0 && s2.length() == 1 && s2.charAt(0) == '*')
        return true;
    
    if (s1.length() == 0 || s2.length() == 0)
        return false;           
    
    if (s1.charAt(0) == s2.charAt(0))
        return samePattern(s1.substring(1), s2.substring(1));
    
    boolean r1 = samePattern(s1, s2.substring(1));
    boolean r2 = samePattern(s1.substring(1), s2);
    
    return r1 || r2;

【讨论】:

以上是关于将字符串与通配符模式匹配的递归函数的主要内容,如果未能解决你的问题,请参考以下文章

51正则表达式匹配

在 AWK 模式中添加当前日期和时间与通配符匹配

正则表达式匹配

Ruby Dir 类和方法

将结构与一组模式匹配

RubyDir 类和方法