Here's a naive, but tested and working implementation using recursion. I think its O(n^3) because of the three cases on the asterik. There a decent number of corner cases in this problem.
Dr. Dobbs has a decent writeup on this problem: http://www.drdobbs.com/architecture-and-design/matching-wildcards-an-algorithm/210200888
Though one of the comments hinted at using Boyer-Moore, it doesn't look like regular Boyer-Moore supports wildcards. There's a blog post that added wildcards to KMP string search - http://0x7fffffff.blogspot.com/2012/07/kmp-supporting-wild-card.html
from collections import namedtuple
import string
import unittest
LITERAL_CHARS = set(string.ascii_letters + string.digits)
def simple_matcher(pattern, string):
if pattern == "":
if string == "":
return True
else:
return False
# We have a non-empty pattern
else:
if string == "":
# If we only have * in the pattern, we can match the empty string.
if all(p == "*" for p in pattern):
return True
else:
return False
# We have a non-empty pattern and string
else:
if pattern[0] in LITERAL_CHARS:
return (pattern [0] == string [0]
and simple_matcher(pattern[1:], string[1:]))
elif pattern[0] == "?":
return simple_matcher(pattern[1:], string[1:])
elif pattern[0] == "*":
return (
# Match one char with * and keep going
simple_matcher(pattern[0:], string[1:])
or
# Don't use the *
simple_matcher(pattern[1:], string[0:])
or
# Only match one char with *
simple_matcher(pattern[1:], string[1:]))
Result = namedtuple('Result', ['pattern', 'string', 'expected'])
class TestSimpleMatcher(unittest.TestCase):
def test_empty_string(self):
results = [Result("", "", True),
Result("a", "", False),
Result("?", "", False),
Result("*", "", True),
Result("**", "", True),
Result("***", "", True)]
for pattern, string, expected in results:
self.assertEqual(simple_matcher(pattern, string), expected)
def test_empty_pattern(self):
results = [Result("", "", True),
Result("", "a", False),
Result("", "1", False),
Result("", "A", False),
Result("", "aaasdfasdf", False)]
for pattern, string, expected in results:
self.assertEqual(simple_matcher(pattern, string), expected)
def test_literals_against_one_char_string(self):
results = [
Result("a", "a", True),
Result("A", "A", True),
Result("1", "1", True),
Result("2", "a", False),
Result("4", "A", False),
Result("B", "1", False)
]
for pattern, string, expected in results:
self.assertEqual(simple_matcher(pattern, string), expected)
def test_question_marks_against_one_char_string(self):
results = [
Result("?", "a", True),
Result("?", "A", True),
Result("?", "1", True),
Result("??", "a", False),
Result("??", "A", False),
Result("??", "1", False),
]
for pattern, string, expected in results:
self.assertEqual(simple_matcher(pattern, string), expected)
def test_asteriks_against_one_char_string(self):
results = [
Result("*", "a", True),
Result("*", "A", True),
Result("*", "1", True),
Result("**", "a", True),
Result("***", "a", True),
Result("*****", "a", True),
]
for pattern, string, expected in results:
self.assertEqual(simple_matcher(pattern, string), expected)
def test_combinations_against_one_char_string(self):
results = [
Result("*?", "a", True),
Result("*A", "A", True),
Result("*?*", "a", True),
Result("*A*", "A", True),
Result("*?a", "a", False),
Result("*B", "A", False),
Result("*??*", "a", False),
Result("*A*?", "A", False),
]
for pattern, string, expected in results:
self.assertEqual(simple_matcher(pattern, string), expected)
def test_combinations_against_three_char_string(self):
results = [
Result("*?", "aaa", True),
Result("???", "aaa", True),
Result("?*A", "BAA", True),
Result("*E*", "1EF", True),
Result("*", "123", True),
Result("****", "123", True),
Result("*?a*", "abc", False),
Result("*B", "Bod", False),
Result("????", "joe", False),
]
for pattern, string, expected in results:
self.assertEqual(simple_matcher(pattern, string), expected)
if __name__ == '__main__':
unittest.main()