|
1 | 1 | package com.thealgorithms.others;
|
2 | 2 |
|
| 3 | +import java.util.Set; |
| 4 | +import java.util.TreeSet; |
| 5 | + |
3 | 6 | /**
|
4 |
| - * @author Prateek Kumar Oraon (https://github.com/prateekKrOraon) |
| 7 | + * A class to perform string matching using <a href="https://en.wikipedia.org/wiki/Finite-state_machine">finite automata</a>. |
| 8 | + * |
| 9 | + * @author <a href="https://github.com/prateekKrOraon">Prateek Kumar Oraon</a> |
5 | 10 | */
|
6 |
| -import java.util.Scanner; |
7 |
| - |
8 |
| -// An implementation of string matching using finite automata |
9 | 11 | public final class StringMatchFiniteAutomata {
|
10 |
| - private StringMatchFiniteAutomata() { |
11 |
| - } |
12 |
| - |
13 |
| - public static final int CHARS = 256; |
14 |
| - public static int[][] fa; |
15 |
| - public static Scanner scanner = null; |
16 |
| - |
17 |
| - public static void main(String[] args) { |
18 |
| - scanner = new Scanner(System.in); |
19 |
| - System.out.println("Enter String"); |
20 |
| - String text = scanner.nextLine(); |
21 |
| - System.out.println("Enter pattern"); |
22 |
| - String pat = scanner.nextLine(); |
23 | 12 |
|
24 |
| - searchPat(text, pat); |
| 13 | + // Constants |
| 14 | + private static final int CHARS = Character.MAX_VALUE + 1; // Total number of characters in the input alphabet |
25 | 15 |
|
26 |
| - scanner.close(); |
| 16 | + // Private constructor to prevent instantiation |
| 17 | + private StringMatchFiniteAutomata() { |
27 | 18 | }
|
28 | 19 |
|
29 |
| - public static void searchPat(String text, String pat) { |
30 |
| - int m = pat.length(); |
31 |
| - int n = text.length(); |
32 |
| - |
33 |
| - fa = new int[m + 1][CHARS]; |
34 |
| - |
35 |
| - computeFA(pat, m, fa); |
36 |
| - |
37 |
| - int state = 0; |
38 |
| - for (int i = 0; i < n; i++) { |
39 |
| - state = fa[state][text.charAt(i)]; |
40 |
| - |
41 |
| - if (state == m) { |
42 |
| - System.out.println("Pattern found at index " + (i - m + 1)); |
| 20 | + /** |
| 21 | + * Searches for the pattern in the given text using finite automata. |
| 22 | + * |
| 23 | + * @param text The text to search within. |
| 24 | + * @param pattern The pattern to search for. |
| 25 | + */ |
| 26 | + public static Set<Integer> searchPattern(final String text, final String pattern) { |
| 27 | + final var stateTransitionTable = computeStateTransitionTable(pattern); |
| 28 | + FiniteAutomata finiteAutomata = new FiniteAutomata(stateTransitionTable); |
| 29 | + |
| 30 | + Set<Integer> indexFound = new TreeSet<>(); |
| 31 | + for (int i = 0; i < text.length(); i++) { |
| 32 | + finiteAutomata.consume(text.charAt(i)); |
| 33 | + |
| 34 | + if (finiteAutomata.getState() == pattern.length()) { |
| 35 | + indexFound.add(i - pattern.length() + 1); |
43 | 36 | }
|
44 | 37 | }
|
| 38 | + return indexFound; |
45 | 39 | }
|
46 | 40 |
|
47 |
| - // Computes finite automata for the pattern |
48 |
| - public static void computeFA(String pat, int m, int[][] fa) { |
49 |
| - for (int state = 0; state <= m; ++state) { |
| 41 | + /** |
| 42 | + * Computes the finite automata table for the given pattern. |
| 43 | + * |
| 44 | + * @param pattern The pattern to preprocess. |
| 45 | + * @return The state transition table. |
| 46 | + */ |
| 47 | + private static int[][] computeStateTransitionTable(final String pattern) { |
| 48 | + final int patternLength = pattern.length(); |
| 49 | + int[][] stateTransitionTable = new int[patternLength + 1][CHARS]; |
| 50 | + |
| 51 | + for (int state = 0; state <= patternLength; ++state) { |
50 | 52 | for (int x = 0; x < CHARS; ++x) {
|
51 |
| - fa[state][x] = getNextState(pat, m, state, x); |
| 53 | + stateTransitionTable[state][x] = getNextState(pattern, patternLength, state, x); |
52 | 54 | }
|
53 | 55 | }
|
| 56 | + |
| 57 | + return stateTransitionTable; |
54 | 58 | }
|
55 | 59 |
|
56 |
| - public static int getNextState(String pat, int m, int state, int x) { |
57 |
| - // if current state is less than length of pattern |
58 |
| - // and input character of pattern matches the character in the alphabet |
59 |
| - // then automata goes to next state |
60 |
| - if (state < m && x == pat.charAt(state)) { |
| 60 | + /** |
| 61 | + * Gets the next state for the finite automata. |
| 62 | + * |
| 63 | + * @param pattern The pattern being matched. |
| 64 | + * @param patternLength The length of the pattern. |
| 65 | + * @param state The current state. |
| 66 | + * @param x The current character from the input alphabet. |
| 67 | + * @return The next state. |
| 68 | + */ |
| 69 | + private static int getNextState(final String pattern, final int patternLength, final int state, final int x) { |
| 70 | + // If the current state is less than the length of the pattern |
| 71 | + // and the character matches the pattern character, go to the next state |
| 72 | + if (state < patternLength && x == pattern.charAt(state)) { |
61 | 73 | return state + 1;
|
62 | 74 | }
|
63 | 75 |
|
| 76 | + // Check for the highest prefix which is also a suffix |
64 | 77 | for (int ns = state; ns > 0; ns--) {
|
65 |
| - if (pat.charAt(ns - 1) == x) { |
| 78 | + if (pattern.charAt(ns - 1) == x) { |
| 79 | + boolean match = true; |
66 | 80 | for (int i = 0; i < ns - 1; i++) {
|
67 |
| - if (pat.charAt(i) != pat.charAt(state - ns + i + 1)) { |
| 81 | + if (pattern.charAt(i) != pattern.charAt(state - ns + i + 1)) { |
| 82 | + match = false; |
68 | 83 | break;
|
69 | 84 | }
|
70 |
| - |
71 |
| - if (i == ns - 1) { |
72 |
| - return ns; |
73 |
| - } |
| 85 | + } |
| 86 | + if (match) { |
| 87 | + return ns; |
74 | 88 | }
|
75 | 89 | }
|
76 | 90 | }
|
77 | 91 |
|
| 92 | + // If no prefix which is also a suffix is found, return 0 |
78 | 93 | return 0;
|
79 | 94 | }
|
| 95 | + |
| 96 | + /** |
| 97 | + * A class representing the finite automata for pattern matching. |
| 98 | + */ |
| 99 | + private static final class FiniteAutomata { |
| 100 | + private int state = 0; |
| 101 | + private final int[][] stateTransitionTable; |
| 102 | + |
| 103 | + private FiniteAutomata(int[][] stateTransitionTable) { |
| 104 | + this.stateTransitionTable = stateTransitionTable; |
| 105 | + } |
| 106 | + |
| 107 | + /** |
| 108 | + * Consumes an input character and transitions to the next state. |
| 109 | + * |
| 110 | + * @param input The input character. |
| 111 | + */ |
| 112 | + private void consume(final char input) { |
| 113 | + state = stateTransitionTable[state][input]; |
| 114 | + } |
| 115 | + |
| 116 | + /** |
| 117 | + * Gets the current state of the finite automata. |
| 118 | + * |
| 119 | + * @return The current state. |
| 120 | + */ |
| 121 | + private int getState() { |
| 122 | + return state; |
| 123 | + } |
| 124 | + } |
80 | 125 | }
|
0 commit comments