patternjavaModerate
Deterministic finite automaton in Java
Viewed 0 times
automatonfinitedeterministicjava
Problem
Formally, a deterministic finite automaton is a 5-tuple \$M = (Q, \Sigma, \delta, q_0, F)\$, where
Basically, each DFA represents a so-called regular language \$L\$, and given input string \$s\$, answers whether \$s \in L\$. This is done in \$\mathcal{O}(|s|)\$ time simply by consuming each character and making the state transitions starting from \$q_0\$; if after reading the entire string we end up in a state in \$F\$, the DFA accepts the string.
Please, tell me anything that comes to mind.
TransitionFunction.java
DFA.java
```
package net.coderodde.dfa;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
/**
* This class implements a
*
* deterministic finite automaton.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 2, 2016)
*/
public class DFA {
private final TransitionFunction transitionFunction;
private final int startState;
private final Set acceptingStates;
- \$Q\$ is the set of all possible states
- \$\Sigma\$ is the alphabet
- \$\delta \colon Q \times \Sigma \to Q\$ is the transition function
- \$q_0\$ is the starting state
- \$F\$ is the set of accepting states
Basically, each DFA represents a so-called regular language \$L\$, and given input string \$s\$, answers whether \$s \in L\$. This is done in \$\mathcal{O}(|s|)\$ time simply by consuming each character and making the state transitions starting from \$q_0\$; if after reading the entire string we end up in a state in \$F\$, the DFA accepts the string.
Please, tell me anything that comes to mind.
TransitionFunction.java
package net.coderodde.dfa;
import java.util.HashMap;
import java.util.Map;
/**
* This class implements a transition function.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 2, 2016)
*/
public class TransitionFunction {
private final Map> function =
new HashMap<>();
public void setTransition(Integer startState,
Integer goalState,
char character) {
function.putIfAbsent(startState, new HashMap<>());
function.get(startState).put(character, goalState);
}
public Integer process(Integer startState, char character) {
if (!function.containsKey(startState)) {
return null;
}
return function.get(startState).get(character);
}
}DFA.java
```
package net.coderodde.dfa;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Scanner;
import java.util.Set;
/**
* This class implements a
*
* deterministic finite automaton.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 2, 2016)
*/
public class DFA {
private final TransitionFunction transitionFunction;
private final int startState;
private final Set acceptingStates;
Solution
Java 8 constructs
The following
can be simplified by using the
Also, the following
can be also written more simply with the
When the map doesn't contain the key
Code structure
You currently have a class
I find the name
Why not rename the class
This is what the class could look like
Notice that the
With this change, the
Using
You are currently relying on
Then, the
The following
function.putIfAbsent(startState, new HashMap<>());
function.get(startState).put(character, goalState);can be simplified by using the
computeIfAbsent(key, mappingFunction) method. This method will return the current value associated with the given key or compute a new value for that key and return it. As such, you could have:function.computeIfAbsent(startState, k -> new HashMap<>()).put(character, goalState);Also, the following
if (!function.containsKey(startState)) {
return null;
}
return function.get(startState).get(character);can be also written more simply with the
getOrDefault(key, defaultValue) method. This will return the value associated with the given key or else return the default value. You could have:return function.getOrDefault(startState, Collections.emptyMap()).get(character);When the map doesn't contain the key
startState, an empty map (that was already instantiated, it is a cached value) is returned with Collections.emptyMap().Code structure
You currently have a class
TransitionFunction that holds all transitions and offers a method to go from one state to another for a particular character.I find the name
TransitionFunction a bit misleading because it would imply that the class is in fact a Function in that it is a functional interface taking a parameter and returing a value. But it is more than that since it is the class that holds all possible transitions.Why not rename the class
Transitions and:- Rename the method
setTransitiontoaddTransitionto better reflect the fact that this method adds a transition (instead of setting it).
- Move the code for processing all transitions, that is currently placed inside the
matchesmethod, in this class. This method would be calledprocessAll(Integer startState, char[] characters)and would process all the characters given starting at the given state.
This is what the class could look like
public class Transitions {
private final Map> function = new HashMap<>();
public void addTransition(Integer startState, Integer goalState, char character) {
function.computeIfAbsent(startState, k -> new HashMap<>()).put(character, goalState);
}
public Integer process(Integer startState, char character) {
return function.getOrDefault(startState, Collections.emptyMap()).get(character);
}
public Integer processAll(Integer startState, char[] characters) {
return IntStream.range(0, characters.length)
.boxed()
.reduce(startState, (s, i) -> s == null ? null : process(s, characters[i]));
}
}Notice that the
processAll method leverages an IntStream over the indexes of the input array. The final state is calculating by reducing from the starting state and calling process each time on the accumulated result. The difference between your initial code is that it is not short-circuiting; if you wish to keep that, you can keep your for loop as-is.With this change, the
matches method would just be:public boolean matches(String text) {
Integer finalState = transitionFunction.processAll(startState, text.toCharArray());
return finalState != null && acceptingStates.contains(finalState);
}Using
OptionalYou are currently relying on
null values to mean "no state". Using an Optional would remove all that. We could make the methods process and processAll return an Optional withpublic Optional process(Integer startState, char character) {
return Optional.ofNullable(function.getOrDefault(startState, Collections.emptyMap()).get(character));
}
public Optional processAll(Integer startState, char[] characters) {
return IntStream.range(0, characters.length)
.boxed()
.reduce(
Optional.of(startState),
(o, i) -> o.flatMap(s -> process(s, characters[i])),
(o1, o2) -> o1.map(Optional::of).orElse(o2)
);
}process makes a call to Optional.ofNullable to wrap the value. The reducing part works on the optional values. The combiners combines two optional into a single one.Then, the
matches method becomespublic boolean matches(String text) {
return transitionFunction.processAll(startState, text.toCharArray())
.map(acceptingStates::contains)
.orElse(false);
}Code Snippets
function.putIfAbsent(startState, new HashMap<>());
function.get(startState).put(character, goalState);function.computeIfAbsent(startState, k -> new HashMap<>()).put(character, goalState);if (!function.containsKey(startState)) {
return null;
}
return function.get(startState).get(character);return function.getOrDefault(startState, Collections.emptyMap()).get(character);public class Transitions {
private final Map<Integer, Map<Character, Integer>> function = new HashMap<>();
public void addTransition(Integer startState, Integer goalState, char character) {
function.computeIfAbsent(startState, k -> new HashMap<>()).put(character, goalState);
}
public Integer process(Integer startState, char character) {
return function.getOrDefault(startState, Collections.emptyMap()).get(character);
}
public Integer processAll(Integer startState, char[] characters) {
return IntStream.range(0, characters.length)
.boxed()
.reduce(startState, (s, i) -> s == null ? null : process(s, characters[i]));
}
}Context
StackExchange Code Review Q#124555, answer score: 10
Revisions (0)
No revisions yet.