Skip to content

Commit f375079

Browse files
feat_wildcard_pattern
1 parent c798337 commit f375079

File tree

1 file changed

+270
-0
lines changed

1 file changed

+270
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
# Wildcard Pattern Matching using Dynamic Programming
2+
#
3+
# Matches a text string against a pattern containing wildcards:
4+
# '*' matches any sequence of characters (including empty sequence)
5+
# '?' matches exactly one character
6+
#
7+
# Time Complexity: O(m * n) where m is text length, n is pattern length
8+
# Space Complexity: O(m * n) for DP table
9+
#
10+
# Input: text string and pattern string with wildcards
11+
# Output: TRUE if pattern matches text, FALSE otherwise
12+
13+
isMatch <- function(text, pattern) {
14+
m <- nchar(text)
15+
n <- nchar(pattern)
16+
17+
# DP table: dp[i][j] = TRUE if text[1:i] matches pattern[1:j]
18+
dp <- matrix(FALSE, nrow = m + 1, ncol = n + 1)
19+
20+
# Empty pattern matches empty text
21+
dp[1, 1] <- TRUE
22+
23+
# Handle patterns starting with '*'
24+
for (j in 2:(n + 1)) {
25+
if (substr(pattern, j - 1, j - 1) == "*") {
26+
dp[1, j] <- dp[1, j - 1]
27+
}
28+
}
29+
30+
# Fill DP table
31+
for (i in 2:(m + 1)) {
32+
for (j in 2:(n + 1)) {
33+
text_char <- substr(text, i - 1, i - 1)
34+
pattern_char <- substr(pattern, j - 1, j - 1)
35+
36+
if (pattern_char == "*") {
37+
# '*' matches empty or any sequence
38+
dp[i, j] <- dp[i, j - 1] || dp[i - 1, j]
39+
} else if (pattern_char == "?" || pattern_char == text_char) {
40+
# '?' or exact match
41+
dp[i, j] <- dp[i - 1, j - 1]
42+
}
43+
}
44+
}
45+
46+
return(dp[m + 1, n + 1])
47+
}
48+
49+
# Space-optimized version using two rows
50+
isMatch_optimized <- function(text, pattern) {
51+
m <- nchar(text)
52+
n <- nchar(pattern)
53+
54+
# Use two rows instead of full table
55+
prev <- rep(FALSE, n + 1)
56+
curr <- rep(FALSE, n + 1)
57+
58+
prev[1] <- TRUE
59+
60+
for (j in 2:(n + 1)) {
61+
if (substr(pattern, j - 1, j - 1) == "*") {
62+
prev[j] <- prev[j - 1]
63+
}
64+
}
65+
66+
for (i in 2:(m + 1)) {
67+
curr <- rep(FALSE, n + 1)
68+
text_char <- substr(text, i - 1, i - 1)
69+
70+
for (j in 2:(n + 1)) {
71+
pattern_char <- substr(pattern, j - 1, j - 1)
72+
73+
if (pattern_char == "*") {
74+
curr[j] <- curr[j - 1] || prev[j]
75+
} else if (pattern_char == "?" || pattern_char == text_char) {
76+
curr[j] <- prev[j - 1]
77+
}
78+
}
79+
80+
prev <- curr
81+
}
82+
83+
return(curr[n + 1])
84+
}
85+
86+
# Backtracking solution (alternative approach)
87+
isMatch_backtrack <- function(text, pattern) {
88+
match_helper <- function(t_idx, p_idx) {
89+
# Base cases
90+
if (p_idx > nchar(pattern)) {
91+
return(t_idx > nchar(text))
92+
}
93+
94+
if (t_idx > nchar(text)) {
95+
# Check if remaining pattern is all '*'
96+
while (p_idx <= nchar(pattern)) {
97+
if (substr(pattern, p_idx, p_idx) != "*") {
98+
return(FALSE)
99+
}
100+
p_idx <- p_idx + 1
101+
}
102+
return(TRUE)
103+
}
104+
105+
pattern_char <- substr(pattern, p_idx, p_idx)
106+
text_char <- substr(text, t_idx, t_idx)
107+
108+
if (pattern_char == "*") {
109+
# Try matching empty or any sequence
110+
return(match_helper(t_idx, p_idx + 1) || match_helper(t_idx + 1, p_idx))
111+
} else if (pattern_char == "?" || pattern_char == text_char) {
112+
return(match_helper(t_idx + 1, p_idx + 1))
113+
} else {
114+
return(FALSE)
115+
}
116+
}
117+
118+
return(match_helper(1, 1))
119+
}
120+
121+
# Find all matching substrings
122+
find_matches <- function(text, pattern) {
123+
matches <- list()
124+
n <- nchar(text)
125+
126+
for (start in 1:n) {
127+
for (end in start:n) {
128+
substring <- substr(text, start, end)
129+
if (isMatch(substring, pattern)) {
130+
matches[[length(matches) + 1]] <- list(
131+
start = start,
132+
end = end,
133+
text = substring
134+
)
135+
}
136+
}
137+
}
138+
139+
return(matches)
140+
}
141+
142+
# Count matching patterns
143+
count_matches <- function(texts, pattern) {
144+
count <- 0
145+
for (text in texts) {
146+
if (isMatch(text, pattern)) {
147+
count <- count + 1
148+
}
149+
}
150+
return(count)
151+
}
152+
153+
# Example usage and tests
154+
cat("=== Wildcard Pattern Matching ===\n\n")
155+
156+
# Test cases
157+
test_cases <- list(
158+
list(text = "aa", pattern = "a", expected = FALSE),
159+
list(text = "aa", pattern = "*", expected = TRUE),
160+
list(text = "cb", pattern = "?a", expected = FALSE),
161+
list(text = "adceb", pattern = "*a*b", expected = TRUE),
162+
list(text = "acdcb", pattern = "a*c?b", expected = FALSE),
163+
list(text = "abc", pattern = "abc", expected = TRUE),
164+
list(text = "abc", pattern = "a?c", expected = TRUE),
165+
list(text = "abc", pattern = "a*c", expected = TRUE),
166+
list(text = "", pattern = "*", expected = TRUE),
167+
list(text = "", pattern = "?", expected = FALSE),
168+
list(text = "mississippi", pattern = "m*iss*p*", expected = TRUE),
169+
list(text = "hello", pattern = "h*o", expected = TRUE),
170+
list(text = "world", pattern = "w?r*", expected = TRUE),
171+
list(text = "test", pattern = "t??t", expected = TRUE),
172+
list(text = "abcdef", pattern = "a*f", expected = TRUE)
173+
)
174+
175+
cat("Running test cases:\n\n")
176+
passed <- 0
177+
failed <- 0
178+
179+
for (i in seq_along(test_cases)) {
180+
tc <- test_cases[[i]]
181+
result <- isMatch(tc$text, tc$pattern)
182+
status <- if (result == tc$expected) "PASS" else "FAIL"
183+
184+
if (result == tc$expected) {
185+
passed <- passed + 1
186+
} else {
187+
failed <- failed + 1
188+
}
189+
190+
cat(sprintf("Test %d: text='%s', pattern='%s' => %s [%s]\n",
191+
i, tc$text, tc$pattern, result, status))
192+
}
193+
194+
cat(sprintf("\nResults: %d passed, %d failed out of %d tests\n\n",
195+
passed, failed, length(test_cases)))
196+
197+
# Example: Complex patterns
198+
cat("Complex Pattern Examples:\n")
199+
200+
examples <- list(
201+
list(text = "programming", pattern = "pro*ing"),
202+
list(text = "dynamic", pattern = "d?n?m?c"),
203+
list(text = "algorithm", pattern = "*gor*"),
204+
list(text = "computer", pattern = "c*t*r"),
205+
list(text = "science", pattern = "s*e*e")
206+
)
207+
208+
for (ex in examples) {
209+
result <- isMatch(ex$text, ex$pattern)
210+
cat(sprintf(" '%s' matches '%s': %s\n", ex$text, ex$pattern, result))
211+
}
212+
213+
# Example: Space-optimized version comparison
214+
cat("\nSpace-Optimized Version Test:\n")
215+
text1 <- "abcdefghij"
216+
pattern1 <- "a*f*j"
217+
result_normal <- isMatch(text1, pattern1)
218+
result_optimized <- isMatch_optimized(text1, pattern1)
219+
cat(sprintf("Text: '%s', Pattern: '%s'\n", text1, pattern1))
220+
cat(sprintf("Normal DP: %s, Optimized: %s\n", result_normal, result_optimized))
221+
222+
# Example: Multiple texts matching
223+
cat("\nMatching Multiple Texts:\n")
224+
texts <- c("cat", "bat", "rat", "hat", "mat", "sat")
225+
pattern2 <- "?at"
226+
cat(sprintf("Pattern: '%s'\n", pattern2))
227+
cat("Matching texts:\n")
228+
for (text in texts) {
229+
if (isMatch(text, pattern2)) {
230+
cat(sprintf(" - %s\n", text))
231+
}
232+
}
233+
234+
# Example: Wildcard star patterns
235+
cat("\nWildcard Star Patterns:\n")
236+
files <- c("document.txt", "image.png", "script.r", "data.csv", "report.pdf")
237+
pattern3 <- "*.txt"
238+
cat(sprintf("Pattern: '%s'\n", pattern3))
239+
cat("Matching files:\n")
240+
for (file in files) {
241+
if (isMatch(file, pattern3)) {
242+
cat(sprintf(" - %s\n", file))
243+
}
244+
}
245+
246+
pattern4 <- "*.r"
247+
cat(sprintf("\nPattern: '%s'\n", pattern4))
248+
cat("Matching files:\n")
249+
for (file in files) {
250+
if (isMatch(file, pattern4)) {
251+
cat(sprintf(" - %s\n", file))
252+
}
253+
}
254+
255+
# Example: Edge cases
256+
cat("\nEdge Cases:\n")
257+
edge_cases <- list(
258+
list(text = "", pattern = ""),
259+
list(text = "a", pattern = ""),
260+
list(text = "", pattern = "a"),
261+
list(text = "***", pattern = "*"),
262+
list(text = "aaa", pattern = "a*a")
263+
)
264+
265+
for (ec in edge_cases) {
266+
result <- isMatch(ec$text, ec$pattern)
267+
cat(sprintf(" text='%s', pattern='%s' => %s\n", ec$text, ec$pattern, result))
268+
}
269+
270+
cat("\n=== All tests completed ===\n")

0 commit comments

Comments
 (0)