Skip to content

Commit

Permalink
chore: passed existing tests, add more cases for combinations
Browse files Browse the repository at this point in the history
  • Loading branch information
jcosentino11 committed Sep 18, 2024
1 parent 9bab92b commit c1f6d9f
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

@RequiredArgsConstructor
public class WildcardTrie {

private static final Function<WildcardType, UnsupportedOperationException> EXCEPTION_UNSUPPORTED_WILDCARD_TYPE = wildcardType ->
new UnsupportedOperationException("wildcard type " + wildcardType.name() + " not supported");

private Node root;

private final MatchOptions opts;
Expand All @@ -42,14 +46,17 @@ private Node withPattern(@NonNull Node n, @NonNull String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (isWildcard(s.charAt(i))) {
// create child node from non-wildcard chars that have been accumulated so far
Node node = token.length() > 0 ? n.children.get(token.toString()) : n;
// create child node for wildcard char itself
WildcardType type = WildcardType.from(c);
Node node = n.children.get(type.val());
node = node.children.get(type.val());
node.wildcardType = type;
if (i == s.length() - 1) {
// we've reached the last token
return node;
}
return withPattern(node, s.substring(token.length() + 2));
return withPattern(node, s.substring(i + 1));
} else {
token.append(c);
}
Expand Down Expand Up @@ -80,55 +87,59 @@ public boolean matches(@NonNull String s) {
return matches(root, s);
}

private boolean matches(@NonNull Node n, String s) {
private boolean matches(@NonNull Node n, @NonNull String s) {
if (n.isTerminal()) {
if (n.isWildcard()) {
switch (n.wildcardType) {
case SINGLE:
return s.length() == 1;
case GLOB:
return true;
default:
throw new UnsupportedOperationException("wildcard type " + n.wildcardType.name() + " not supported");
}
} else {
return s.isEmpty();
return n.wildcardType == WildcardType.GLOB || s.isEmpty();
}

if (n.isWildcard()) {
switch (n.wildcardType) {
case SINGLE:
return n.children.keySet().stream().anyMatch(token -> {
Node child = n.children.get(token);
// skip over one character for single wildcard
if (child.isWildcard()) {
return !s.isEmpty() && matches(child, s.substring(1));
} else {
return !s.isEmpty() && s.startsWith(token.substring(0, 1)) && matches(child, s.substring(1));
}
});
case GLOB:
return n.children.keySet().stream().anyMatch(token -> {
Node child = n.children.get(token);
if (child.isWildcard()) {
return true;// TODO
} else {
// consume the input string to find a match
return allIndicesOf(s, token).stream()
.anyMatch(tokenIndex ->
matches(child, s.substring(tokenIndex + token.length()))
);
}
});
default:
throw EXCEPTION_UNSUPPORTED_WILDCARD_TYPE.apply(n.wildcardType);
}
}

for (String token : n.children.keySet()) {
return n.children.keySet().stream().anyMatch(token -> {
Node child = n.children.get(token);

if (n.isWildcard()) { // parent is a wildcard
switch (n.wildcardType) {
if (child.isWildcard()) {
switch (child.wildcardType) {
case SINGLE:
// skip over one character for single wildcard
return matches(child, s.substring(1));
// skip past the next character for ? matching
return !s.isEmpty() && matches(child, s.substring(1));
case GLOB:
// consume the input string to find a match
return allIndicesOf(s, token).stream()
.anyMatch(tokenIndex ->
matches(child, s.substring(tokenIndex + token.length()))
);
// skip past token and figure out retroactively if the glob matched
return matches(child, s);
default:
throw new UnsupportedOperationException("wildcard type " + n.wildcardType.name() + " not supported");
throw EXCEPTION_UNSUPPORTED_WILDCARD_TYPE.apply(child.wildcardType);
}
}

if (child.isWildcard()) {
// skip past the wildcard node,
// on the next iteration we need to figure out
// the part the wildcard matched (if at all).
return matches(child, s);
} else {
// match found, keep following this trie branch
if (s.startsWith(token)) {
return matches(child, s.substring(token.length()));
}
return s.startsWith(token) && matches(child, s.substring(token.length()));
}
}

return false;
});
}

private static List<Integer> allIndicesOf(@NonNull String s, @NonNull String sub) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.params.provider.Arguments.arguments;

// TODO fix failing cases

class WildcardTrieTest {

static Stream<Arguments> validMatches() {
Expand All @@ -36,7 +36,14 @@ static Stream<Arguments> validMatches() {
// single character wildcard ?
arguments("?", asList("f", "*", "?")),
arguments("??", asList("ff", "**", "??", "*?")),
arguments("?f?", asList("fff", "f", "ff", "*f", "*f*", "f*"))
arguments("?f?", asList("fff", "*f*")),
// glob and single
arguments("?*", asList("?", "*", "a", "??", "**", "ab", "***", "???", "abc")),
arguments("*?", asList("?", "*", "a", "??", "**", "ab", "***", "???", "abc")),
arguments("?*?", asList("??", "**", "aa", "???", "***", "aaa", "????", "****", "aaaa")),
arguments("*?*", asList("?", "*", "a", "??", "**", "ab", "***", "???", "abc")),
arguments("a?*b", asList("acb", "a?b", "a*b", "a?cb")),
arguments("a*?b", asList("acb", "a?b", "a*b", "a?cb"))
);
}

Expand All @@ -45,7 +52,8 @@ static Stream<Arguments> validMatches() {
void GIVEN_trie_with_wildcards_WHEN_valid_matches_provided_THEN_pass(String pattern, List<String> matches) {
WildcardTrie.MatchOptions opts = WildcardTrie.MatchOptions.builder().useSingleCharWildcard(true).build();
WildcardTrie trie = new WildcardTrie(opts).withPattern(pattern);
matches.forEach(m -> assertTrue(trie.matches(m)));
matches.forEach(m -> assertTrue(trie.matches(m),
String.format("String \"%s\" did not match the pattern \"%s\"", m, pattern)));
}


Expand All @@ -63,7 +71,12 @@ static Stream<Arguments> invalidMatches() {
// single character wildcard ?
arguments("?", asList("aa", "??", "**")),
arguments("??", asList("aaa", "???", "***")),
arguments("?a?", asList("aaaa", "fff"))
arguments("?a?", asList("fff", "aaaa")),
arguments("?f?", asList("ff", "f", "*f", "f*")),
// glob and single
arguments("?*?", asList("a", "?", "*")),
arguments("a?*b", asList("ab", "abc")),
arguments("a*?b", asList("ab", "abc"))
);
}

Expand All @@ -72,6 +85,7 @@ static Stream<Arguments> invalidMatches() {
void GIVEN_trie_with_wildcards_WHEN_invalid_matches_provided_THEN_fail(String pattern, List<String> matches) {
WildcardTrie.MatchOptions opts = WildcardTrie.MatchOptions.builder().useSingleCharWildcard(true).build();
WildcardTrie trie = new WildcardTrie(opts).withPattern(pattern);
matches.forEach(m -> assertFalse(trie.matches(m)));
matches.forEach(m -> assertFalse(trie.matches(m),
String.format("String \"%s\" incorrectly matched the pattern \"%s\"", m, pattern)));
}
}

0 comments on commit c1f6d9f

Please sign in to comment.