package basiclearner; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import net.automatalib.words.Word; /** * @author Ramon Janssen * * @param the input type of the observations * @param the output type of the observations */ public class ObservationTree { private final ObservationTree parent; private final I parentInput; private final O parentOutput; private final Map> children; private final Map outputs; public ObservationTree() { this(null, null, null); } private ObservationTree(ObservationTree parent, I parentInput, O parentOutput) { this.children = new HashMap<>(); this.outputs = new HashMap<>(); this.parent = parent; this.parentInput = parentInput; this.parentOutput = parentOutput; } /** * @return The outputs observed from the root of the tree until this node */ private List getOutputChain() { if (this.parent == null) { return new LinkedList(); } else { List parentChain = this.parent.getOutputChain(); parentChain.add(parentOutput); return parentChain; } } private List getInputChain() { if (this.parent == null) { return new LinkedList(); } else { List parentChain = this.parent.getInputChain(); parentChain.add(this.parentInput); return parentChain; } } /** * Add one input and output symbol and traverse the tree to the next node * @param input * @param output * @return the next node * @throws InconsistencyException */ public ObservationTree addObservation(I input, O output) throws CacheInconsistencyException { O previousOutput = this.outputs.get(input); boolean createNewBranch = previousOutput == null; if (createNewBranch) { // input hasn't been queried before, make a new branch for it and traverse this.outputs.put(input, output); ObservationTree child = new ObservationTree(this, input, output); this.children.put(input, child); return child; } else if (!previousOutput.equals(output)) { // input is inconsistent with previous observations, throw exception List oldOutputChain = this.children.get(input).getOutputChain(); List newOutputChain = this.getOutputChain(); List inputChain = this.getInputChain(); newOutputChain.add(output); throw new CacheInconsistencyException(toWord(inputChain), toWord(oldOutputChain), toWord(newOutputChain)); } else { // input is consistent with previous observations, just traverse return this.children.get(input); } } /** * Add Observation to the tree * @param inputs * @param outputs * @throws CacheInconsistencyException Inconsistency between new and stored observations */ public void addObservation(Word inputs, Word outputs) throws CacheInconsistencyException { addObservation(inputs.asList(), outputs.asList()); } public void addObservation(List inputs, List outputs) throws CacheInconsistencyException { if (inputs.isEmpty() && outputs.isEmpty()) { return; } else if (inputs.isEmpty() || outputs.isEmpty()) { throw new RuntimeException("Input and output words should have the same length:\n" + inputs + "\n" + outputs); } else { I firstInput = inputs.get(0); O firstOutput = outputs.get(0); try { this.addObservation(firstInput, firstOutput) .addObservation(inputs.subList(1, inputs.size()), outputs.subList(1, outputs.size())); } catch (CacheInconsistencyException e) { throw new CacheInconsistencyException(toWord(inputs), e.getOldOutput(), toWord(outputs)); } } } public static Word toWord(List symbolList) { return Word.fromList(symbolList); } }