9
\$\begingroup\$

I have the following function to compare a string with a wildcard string (containing ? and *), as C# doesn't seem to have a builtin function to do it.

 /// <summary>
 /// Compares wildcard to string
 /// </summary>
 /// <param name="WildString">String to compare</param>
 /// <param name="Mask">Wildcard mask (ex: *.jpg)</param>
 /// <returns>True if match found</returns>
 public static bool CompareWildcard(string WildString, string Mask, bool IgnoreCase = true)
 {
 int i = 0, k = 0;
 // Cannot continue with Mask empty
 if (string.IsNullOrEmpty(Mask))
 return false;
 // If WildString is null -> make it an empty string
 if (WildString == null)
 WildString = string.Empty;
 // If Mask is * and WildString isn't empty -> return true
 if (string.Compare(Mask, "*") == 0 && !string.IsNullOrEmpty(WildString))
 return true;
 // If Mask is ? and WildString length is 1 -> return true
 if (string.Compare(Mask, "?") == 0 && WildString.Length == 1)
 return true;
 // If WildString and Mask match -> no need to go any further
 if (string.Compare(WildString, Mask, IgnoreCase) == 0)
 return true;
 while (k != WildString.Length)
 {
 switch (Mask[i])
 {
 case '*':
 if ((i + 1) == Mask.Length)
 return true;
 while (k != WildString.Length)
 {
 if (CompareWildcard(WildString.Substring(k + 1), Mask.Substring(i + 1), IgnoreCase))
 return true;
 k += 1;
 }
 return false;
 case '?':
 break;
 default:
 if (IgnoreCase == false && WildString[k] != Mask[i])
 return false;
 if (IgnoreCase && Char.ToLower(WildString[k]) != Char.ToLower(Mask[i]))
 return false;
 break;
 }
 i += 1;
 k += 1;
 }
 if (k == WildString.Length)
 {
 if (i == Mask.Length || Mask[i] == '*')
 return true;
 }
 return false;
 }

I added the check to make sure Mask isn't null/empty because if it is then switch (Mask[i]) will throw a IndexOutOfRangeException. I'm wondering if there are any other checks I should make or change (specifically during the while loop)?

asked Jun 26, 2014 at 20:20
\$\endgroup\$
3
  • 1
    \$\begingroup\$ why dont you use Regex and you save yourself this hassle codeproject.com/Articles/11556/Converting-Wildcards-to-Regexes \$\endgroup\$ Commented Jun 26, 2014 at 21:21
  • \$\begingroup\$ @SleimanJneidi I want a foolproof method rather then something that has unknown problems with it \$\endgroup\$ Commented Jun 26, 2014 at 21:54
  • \$\begingroup\$ Why do you think that the $Regex$ method "has unknown problems"? What would satisfy you that any solution was "foolproof"? \$\endgroup\$ Commented Jun 30, 2014 at 14:39

3 Answers 3

7
\$\begingroup\$

What you want to do is called "globbing". It's actually quite easy to do with a Regex.

There are a few code smells here. The recursion is the obvious one. The other is the special-cases. When you special-case the single-character wildcard, it suggests that you're thinking about the problem wrong. There's no reason why they would need to -- or even should -- be specialcased.

If you really want to reinvent the wheel, the "right way" is to build a state machine (削除) and backtrack (削除ここまで). I combined @NotPro's answer and @svick's hint to get http://ideone.com/VUDFIw but that uses backtracking. Check out http://swtch.com/~rsc/regexp/regexp1.html to see why backtracking is a bad idea, and how we can get an O(n) state machine.

answered Jun 27, 2014 at 2:38
\$\endgroup\$
7
\$\begingroup\$

Another option, as outlined in this blog is to not re-invent the wheel and use the VB.net LikeString method:

Add a reference to Microsoft.VisualBasic first.

using Microsoft.VisualBasic;
using Microsoft.VisualBasic.CompilerServices;
if(Operators.LikeString("This is just a test", "*just*", CompareMethod.Text))
{
 Console.WriteLine("This matched!");
}
answered Jul 1, 2014 at 3:53
\$\endgroup\$
1
  • 4
    \$\begingroup\$ using Microsoft.VisualBasic in a C# project? o.O \$\endgroup\$ Commented Jul 1, 2014 at 4:36
1
\$\begingroup\$

You still have bug for wildString = "xyzz", mask = "?" .

The best idea is to use Regex but if you want implement your own solution I would use Linq. I think it's make solution much shorter and easier to understand.

static bool CompareWildcard(IEnumerable<char> input, string mask)
{
 for (int i = 0; i < mask.Length; i++)
 {
 switch (mask[i])
 {
 case '?':
 if (!input.Any())
 return false;
 input = input.Skip(1);
 break;
 case '*':
 while (input.Any() && !CompareWildcard(input, mask.Substring(i + 1)))
 input = input.Skip(1);
 break;
 default:
 if (!input.Any() || input.First() != mask[i])
 return false;
 input = input.Skip(1);
 break;
 }
 }
 return ! input.Any(); 
}
answered Jun 27, 2014 at 11:59
\$\endgroup\$
3
  • 1
    \$\begingroup\$ @Snowbody You're right that using Skip() this way is a bad idea (it leads to \$\mathcal{O}(n^2)\$ code), but how exactly do you clone Enumerator? A better way to do this might be to use ImmutableStack or FSharpList. \$\endgroup\$ Commented Jun 28, 2014 at 14:26
  • \$\begingroup\$ @svick Yikes! I committed the cardinal sin of C#, assuming that it worked the same as C++. Thanks for setting me straight. \$\endgroup\$ Commented Jun 30, 2014 at 19:48
  • 1
    \$\begingroup\$ @Snowbody Now that I think about it, if input was string, then its enumerator is CharEnumerator, which can be cloned. But you can't do that with a general IEnumerator<char>. \$\endgroup\$ Commented Jun 30, 2014 at 19:57

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.