After reading @JavaDeveloper's recent question, I was inspired1 to try my hand at writing code to accomplish the same task.
The "rules" for this code (i.e., its intent) is to match a subset of regular expressions, defined as follows:
.
(dot) means a any single character match*
(star) means 0 or more character match?
(question) means previous char is an option i.ecolou?r
matchescolor
andcolour
.
Just to be clear: I'm well aware that this is not how regular expressions are normally defined, and that the fundamental capability of this subset is exactly that--a subset of what full regexes can recognize/match.
As an aside: although tagging a question with both C and C++ is normally a mistake, I believe in this case it's justified. In particular, the match
function is at least intended to be work as either C or C++. The test rig (enabled by defining TEST
when compiling) uses features to specific to C++, but match
itself should not.
Without further ado, my attempt at implementing this functionality:
#include <string.h>
#ifndef __cplusplus
#include <stdbool.h>
#endif
bool match(char const *needle, char const *haystack) {
for (; *needle!='\0'; ++needle) {
switch (*needle) {
case '.': ++haystack;
case '?': break;
case '*': {
size_t max = strlen(needle);
for (size_t i = 0; i < max; i++)
if (match(needle + 1, haystack + i))
return true;
return false;
}
default:
if (*haystack == *needle)
++haystack;
else if (needle[1] != '?')
return false;
}
}
return *haystack == '\0';
}
#ifdef TEST
#include <iostream>
#include <vector>
#include <string>
template<class F>
void assertTrue(F f, char const *needle, char const *hay_stack) {
static const std::string names [] = { "Failure", "Success" };
std::cout << names[f(needle, hay_stack)];
std::cout << " for: " << needle << "->" << hay_stack << "\n";
}
template<class F>
void assertFalse(F f, char const *needle, char const *hay_stack) {
static const std::string names [] = { "Success", "Failure" };
std::cout << names[f(needle, hay_stack)];
std::cout << " for: " << needle << "->" << hay_stack << "\n";
}
int main(){
assertTrue(match,"colou?**rs", "colours");
assertTrue(match,"colou?**rs", "colors");
assertTrue(match,"colou**?rs", "colours");
// assertTrue(RegexToy.match,"colou**?rs", "colors"); <---- exlusive case.
assertTrue(match,"colou?**r", "colour");
assertTrue(match,"colou?**r", "color");
assertTrue(match,"colou?*", "colou");
assertTrue(match,"colou?*", "colo");
assertTrue(match,"colou?r", "colour");
assertTrue(match,"colou?r", "color");
assertTrue(match,"colou?*?r", "colour");
assertTrue(match,"colou?*?r", "color");
// Success cases
assertTrue(match,"", "");
assertTrue(match, "***", "");
std::vector<char const *> regexList { "abc****", "abc", "a*b*c", "****abc", "**a**b**c****", "abc*" };
char const *str1 = "abc";
for (auto regex : regexList)
assertTrue(match,regex, str1);
char const *regex = "abc****";
std::vector<char const *> strList1 { "abcxyz", "abcx", "abc" };
for (auto str : strList1)
assertTrue(match,regex, str);
regex = "***abc";
std::vector<char const *> strList2 { "xyzabc", "xabc", "abc" };
for (auto str : strList2)
assertTrue(match, regex, str);
assertTrue(match, "a.c", "abc");
assertTrue(match, "a*.*c", "abc");
assertTrue(match,"a*.b.*c", "axxbxxbxxc");
// Fail cases.
assertFalse(match,"abc", "abcd");
assertFalse(match,"*a", "abcd");
assertFalse(match,"a", "");
assertFalse(match,".a*c", "abc");
assertFalse(match,"a.*b", "abc");
assertFalse(match,"..", "abc");
assertFalse(match,"", "abc");
}
#endif
- Along with stealing the idea from @JavaDeveloper, I also stole his test rig, with only minor editing. I hope that's not a problem (I think my making attribution to him clear mans it falls within SE's licensing).