001/* Copyright (C) 2013 TU Dortmund 002 * This file is part of AutomataLib, http://www.automatalib.net/. 003 * 004 * AutomataLib is free software; you can redistribute it and/or 005 * modify it under the terms of the GNU Lesser General Public 006 * License version 3.0 as published by the Free Software Foundation. 007 * 008 * AutomataLib is distributed in the hope that it will be useful, 009 * but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 011 * Lesser General Public License for more details. 012 * 013 * You should have received a copy of the GNU Lesser General Public 014 * License along with AutomataLib; if not, see 015 * http://www.gnu.de/documents/lgpl.en.html. 016 */ 017package net.automatalib.incremental.mealy; 018 019import java.util.ArrayDeque; 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.Objects; 027import java.util.Queue; 028 029import net.automatalib.automata.abstractimpl.AbstractDeterministicAutomaton; 030import net.automatalib.automata.concepts.StateIDs; 031import net.automatalib.automata.concepts.TransitionOutput; 032import net.automatalib.automata.graphs.AbstractAutomatonGraph; 033import net.automatalib.automata.transout.MealyMachine; 034import net.automatalib.automata.transout.impl.compact.CompactMealy; 035import net.automatalib.commons.util.Pair; 036import net.automatalib.commons.util.UnionFind; 037import net.automatalib.commons.util.mappings.MutableMapping; 038import net.automatalib.graphs.UniversalGraph; 039import net.automatalib.graphs.concepts.NodeIDs; 040import net.automatalib.graphs.dot.DOTPlottableGraph; 041import net.automatalib.graphs.dot.GraphDOTHelper; 042import net.automatalib.incremental.ConflictException; 043import net.automatalib.incremental.IncrementalConstruction; 044import net.automatalib.words.Alphabet; 045import net.automatalib.words.Word; 046import net.automatalib.words.WordBuilder; 047 048/** 049 * Incrementally builds an (acyclic) Mealy machine, from a set of input and corresponding 050 * output words. 051 * 052 * @author Malte Isberner <malte.isberner@gmail.com> 053 * 054 * @param <I> input symbol class 055 * @param <O> output symbol class 056 */ 057public class IncrementalMealyBuilder<I, O> extends 058 AbstractDeterministicAutomaton<State, I, TransitionRecord> implements 059 TransitionOutput<TransitionRecord, O>, 060 UniversalGraph<State, TransitionRecord, Void, Pair<I,O>>, 061 DOTPlottableGraph<State, TransitionRecord>, 062 IncrementalConstruction<MealyMachine<?,I,?,O>, I> { 063 064 065 private final Map<StateSignature, State> register = new HashMap<>(); 066 067 private final Alphabet<I> inputAlphabet; 068 private final int alphabetSize; 069 private final State init; 070 071 /** 072 * Constructor. 073 * @param inputAlphabet the input alphabet to use 074 */ 075 public IncrementalMealyBuilder(Alphabet<I> inputAlphabet) { 076 this.inputAlphabet = inputAlphabet; 077 this.alphabetSize = inputAlphabet.size(); 078 StateSignature initSig = new StateSignature(alphabetSize); 079 this.init = new State(initSig); 080 register.put(null, init); 081 } 082 083 /* 084 * (non-Javadoc) 085 * @see net.automatalib.automata.abstractimpl.AbstractDeterministicAutomaton#size() 086 */ 087 @Override 088 public int size() { 089 return register.size(); 090 } 091 092 /** 093 * Retrieves the (internal) state reached by the given input word, 094 * or <tt>null</tt> if no information about the input word is present. 095 * @param word the input word 096 * @return the corresponding state 097 */ 098 private State getState(Word<I> word) { 099 State s = init; 100 101 for (I sym : word) { 102 int idx = inputAlphabet.getSymbolIndex(sym); 103 s = s.getSuccessor(idx); 104 if (s == null) 105 return null; 106 } 107 return s; 108 } 109 110 /** 111 * Checks whether there exists secured information about the output 112 * for the given word. 113 * @param word the input word 114 * @return a boolean indicating whether information about the output for the 115 * given input word exists. 116 */ 117 public boolean isComplete(Word<I> word) { 118 State s = getState(word); 119 return (s != null); 120 } 121 122 /** 123 * Retrieves the output word for the given input word. If no definitive information 124 * for the input word exists, the output for the longest known prefix will be returned. 125 * @param word the input word 126 * @param output a {@link WordBuilder} for constructing the output word 127 * @return <tt>true</tt> if the information contained was complete (in this case, 128 * <code>word.length() == output.size()</code> will hold), <tt>false</tt> otherwise. 129 */ 130 @SuppressWarnings("unchecked") 131 public boolean lookup(Word<I> word, WordBuilder<O> output) { 132 State curr = init; 133 for(I sym : word) { 134 int idx = inputAlphabet.getSymbolIndex(sym); 135 State succ = curr.getSuccessor(idx); 136 if(succ == null) 137 return false; 138 output.append((O)curr.getOutput(idx)); 139 curr = succ; 140 } 141 142 return true; 143 } 144 145 146 /** 147 * Incorporates a pair of input/output words into the stored information. 148 * @param word the input word 149 * @param outputWord the corresponding output word 150 * @throws ConflictException if this information conflicts with information already stored 151 */ 152 public void insert(Word<I> word, Word<O> outputWord) { 153 int len = word.length(); 154 155 State curr = init; 156 State conf = null; 157 158 int confIndex = -1; 159 160 int prefixLen = 0; 161 // Find the internal state in the automaton that can be reached by a 162 // maximal prefix of the word (i.e., a path of secured information) 163 for (I sym : word) { 164 // During this, store the *first* confluence state (i.e., state with 165 // multiple incoming edges). 166 if (conf == null && curr.isConfluence()) { 167 conf = curr; 168 confIndex = prefixLen; 169 } 170 171 int idx = inputAlphabet.getSymbolIndex(sym); 172 State succ = curr.getSuccessor(idx); 173 if (succ == null) 174 break; 175 // If a transition exists for the input symbol, it also has an output symbol. 176 // Check if this matches the provided one, otherwise there is a conflict 177 O outSym = outputWord.getSymbol(prefixLen); 178 if(!Objects.equals(outSym, curr.getOutput(idx))) 179 throw new ConflictException("Incompatible output symbols: " + outSym + " vs " + curr.getOutput(idx)); 180 curr = succ; 181 prefixLen++; 182 } 183 184 // The information was already present - we do not need to continue 185 if (prefixLen == len) 186 return; 187 188 189 // We then create a suffix path, i.e., a linear sequence of states corresponding to 190 // the suffix (more precisely: the suffix minus the first symbol, since this is the 191 // transition which is used for gluing the suffix path to the existing automaton). 192 Word<I> suffix = word.subWord(prefixLen); 193 Word<O> suffixOut = outputWord.subWord(prefixLen); 194 195 State last; 196 197 State suffixState; 198 State endpoint = null; 199 if(conf != null) { 200 // If we encountered a confluence state on a way, the whole path including 201 // the confluence state will have to be duplicated to separate it from 202 // other prefixes 203 suffixState = createSuffix(suffix.subWord(1), suffixOut.subWord(1)); 204 } 205 else { 206 // This is a dangerous corner case: If NO confluence state was found, it can happen 207 // that the last state of the suffix path is merged with the end of the prefix path 208 // (i.e., when both have no outgoing transitions - note that this is ALWAYS the case 209 // upon the first insert() call). Because there is no confluence we resolve by cloning 210 // part of the prefix path, we might accidentally introduce a cycle here. 211 // Storing the endpoint of the suffix path allows avoiding this later on. 212 Pair<State,State> suffixRes = createSuffix2(suffix.subWord(1), suffixOut.subWord(1)); 213 suffixState = suffixRes.getFirst(); 214 endpoint = suffixRes.getSecond(); 215 } 216 217 // Here we create the "gluing" transition 218 I sym = suffix.getSymbol(0); 219 int suffTransIdx = inputAlphabet.getSymbolIndex(sym); 220 O suffTransOut = suffixOut.getSymbol(0); 221 222 223 int currentIndex; 224 if (conf != null) { 225 // If there was a confluence state, we have to clone all nodes on 226 // the prefix path up to this state, in order to separate it from other 227 // prefixes reaching the confluence state (we do not now anything about them 228 // plus the suffix). 229 last = clone(curr, suffTransIdx, suffixState, suffTransOut); 230 231 for (int i = prefixLen - 1; i >= confIndex; i--) { 232 State s = getState(word.prefix(i)); 233 sym = word.getSymbol(i); 234 int idx = inputAlphabet.getSymbolIndex(sym); 235 last = clone(s, idx, last); 236 } 237 238 currentIndex = confIndex; 239 } else { 240 // Otherwise, we have to check for the above-mentioned corner case, and possibly 241 // also duplicate the last state on the prefix path 242 if(endpoint == curr) 243 last = clone(curr, suffTransIdx, suffixState, suffTransOut); 244 else if(curr != init) 245 last = updateSignature(curr, suffTransIdx, suffixState, suffTransOut); 246 else { 247 // The last state on the prefix path is the initial state. After updating 248 // its signature, we are done since we cannot backtrack any further. 249 updateInitSignature(suffTransIdx, suffixState, suffTransOut); 250 return; 251 } 252 currentIndex = prefixLen; 253 } 254 255 // Finally, we have to refresh all the signatures, iterating backwards 256 // until the updating becomes stable. 257 while (--currentIndex > 0) { 258 State state = getState(word.prefix(currentIndex)); 259 sym = word.getSymbol(currentIndex); 260 int idx = inputAlphabet.getSymbolIndex(sym); 261 last = updateSignature(state, idx, last); 262 263 if (state == last) 264 return; 265 } 266 267 sym = word.getSymbol(0); 268 int idx = inputAlphabet.getSymbolIndex(sym); 269 updateInitSignature(idx, last); 270 } 271 272 273 /** 274 * Update the signature of the initial state. This requires special handling, as the 275 * initial state is not stored in the register (since it can never legally act as a predecessor). 276 * @param idx the transition index being changed 277 * @param succ the new successor state 278 */ 279 private void updateInitSignature(int idx, State succ) { 280 StateSignature sig = init.getSignature(); 281 State oldSucc = sig.successors[idx]; 282 if(oldSucc == succ) 283 return; 284 if(oldSucc != null) 285 oldSucc.decreaseIncoming(); 286 sig.successors[idx] = succ; 287 succ.increaseIncoming(); 288 } 289 290 /** 291 * Update the signature of a state, changing only the successor state of a single transition 292 * index. 293 * @param state the state which's signature to update 294 * @param idx the transition index to modify 295 * @param succ the new successor state 296 * @return the resulting state, which can either be the same as the input state (if the new 297 * signature is unique), or the result of merging with another state. 298 */ 299 private State updateSignature(State state, int idx, State succ) { 300 StateSignature sig = state.getSignature(); 301 if (sig.successors[idx] == succ) 302 return state; 303 304 register.remove(sig); 305 if(sig.successors[idx] != null) 306 sig.successors[idx].decreaseIncoming(); 307 sig.successors[idx] = succ; 308 succ.increaseIncoming(); 309 sig.updateHashCode(); 310 return replaceOrRegister(state); 311 } 312 313 /** 314 * Updates the signature of the initial state, changing both the successor state 315 * and the output symbol. 316 * @param idx the transition index to change 317 * @param succ the new successor state 318 * @param out the output symbol 319 */ 320 private void updateInitSignature(int idx, State succ, O out) { 321 StateSignature sig = init.getSignature(); 322 State oldSucc = sig.successors[idx]; 323 if(oldSucc == succ && Objects.equals(out, sig.outputs[idx])) 324 return; 325 if(oldSucc != null) 326 oldSucc.decreaseIncoming(); 327 sig.successors[idx] = succ; 328 sig.outputs[idx] = out; 329 succ.increaseIncoming(); 330 } 331 332 /** 333 * Updates the signature of a state, changing both the successor state and the output 334 * symbol for a single transition index. 335 * @param state the state which's signature to change 336 * @param idx the transition index to change 337 * @param succ the new successor state 338 * @param out the output symbol 339 * @return the resulting state, which can either be the same as the input state (if the new 340 * signature is unique), or the result of merging with another state. 341 */ 342 private State updateSignature(State state, int idx, State succ, O out) { 343 StateSignature sig = state.getSignature(); 344 if (sig.successors[idx] == succ && Objects.equals(out, sig.outputs[idx])) 345 return state; 346 347 register.remove(sig); 348 if(sig.successors[idx] != null) 349 sig.successors[idx].decreaseIncoming(); 350 sig.successors[idx] = succ; 351 succ.increaseIncoming(); 352 sig.outputs[idx] = out; 353 sig.updateHashCode(); 354 return replaceOrRegister(state); 355 } 356 357 358 private State clone(State other, int idx, State succ) { 359 StateSignature sig = other.getSignature(); 360 if (sig.successors[idx] == succ) 361 return other; 362 sig = sig.clone(); 363 sig.successors[idx] = succ; 364 sig.updateHashCode(); 365 return replaceOrRegister(sig); 366 } 367 368 private State clone(State other, int idx, State succ, O out) { 369 StateSignature sig = other.getSignature(); 370 if (sig.successors[idx] == succ && Objects.equals(out, sig.outputs[idx])) 371 return other; 372 sig = sig.clone(); 373 sig.successors[idx] = succ; 374 sig.outputs[idx] = out; 375 sig.updateHashCode(); 376 return replaceOrRegister(sig); 377 } 378 379 private State replaceOrRegister(StateSignature sig) { 380 State state = register.get(sig); 381 if (state != null) 382 return state; 383 384 register.put(sig, state = new State(sig)); 385 for (int i = 0; i < sig.successors.length; i++) { 386 State succ = sig.successors[i]; 387 if (succ != null) 388 succ.increaseIncoming(); 389 } 390 return state; 391 } 392 393 private State replaceOrRegister(State state) { 394 StateSignature sig = state.getSignature(); 395 State other = register.get(sig); 396 if (other != null) { 397 if (state != other) { 398 for (int i = 0; i < sig.successors.length; i++) { 399 State succ = sig.successors[i]; 400 if(succ != null) 401 succ.decreaseIncoming(); 402 } 403 } 404 return other; 405 } 406 407 register.put(sig, state); 408 return state; 409 } 410 411 private State createSuffix(Word<I> suffix, Word<O> suffixOut) { 412 StateSignature sig = new StateSignature(alphabetSize); 413 sig.updateHashCode(); 414 State last = replaceOrRegister(sig); 415 416 int len = suffix.length(); 417 for (int i = len - 1; i >= 0; i--) { 418 sig = new StateSignature(alphabetSize); 419 I sym = suffix.getSymbol(i); 420 O outsym = suffixOut.getSymbol(i); 421 int idx = inputAlphabet.getSymbolIndex(sym); 422 sig.successors[idx] = last; 423 sig.outputs[idx] = outsym; 424 sig.updateHashCode(); 425 last = replaceOrRegister(sig); 426 } 427 428 return last; 429 } 430 431 private Pair<State,State> createSuffix2(Word<I> suffix, Word<O> suffixOut) { 432 StateSignature sig = new StateSignature(alphabetSize); 433 sig.updateHashCode(); 434 State last = replaceOrRegister(sig); 435 State end = last; 436 437 int len = suffix.length(); 438 for (int i = len - 1; i >= 0; i--) { 439 sig = new StateSignature(alphabetSize); 440 I sym = suffix.getSymbol(i); 441 O outsym = suffixOut.getSymbol(i); 442 int idx = inputAlphabet.getSymbolIndex(sym); 443 sig.successors[idx] = last; 444 sig.outputs[idx] = outsym; 445 sig.updateHashCode(); 446 last = replaceOrRegister(sig); 447 } 448 449 return Pair.make(last, end); 450 } 451 452 453 454 /* 455 * (non-Javadoc) 456 * @see net.automatalib.ts.TransitionSystem#getSuccessor(java.lang.Object) 457 */ 458 @Override 459 public State getSuccessor(TransitionRecord transition) { 460 return transition.source.getSuccessor(transition.transIdx); 461 } 462 463 /* 464 * (non-Javadoc) 465 * @see net.automatalib.ts.SimpleDTS#getInitialState() 466 */ 467 @Override 468 public State getInitialState() { 469 return init; 470 } 471 472 /* 473 * (non-Javadoc) 474 * @see net.automatalib.graphs.UniversalGraph#getNodeProperties(java.lang.Object) 475 */ 476 @Override 477 public Void getNodeProperty(State node) { 478 return null; 479 } 480 481 /* 482 * (non-Javadoc) 483 * @see net.automatalib.graphs.UniversalGraph#getEdgeProperties(java.lang.Object) 484 */ 485 @Override 486 @SuppressWarnings("unchecked") 487 public Pair<I, O> getEdgeProperty(TransitionRecord edge) { 488 I input = inputAlphabet.getSymbol(edge.transIdx); 489 O out = (O)edge.source.getOutput(edge.transIdx); 490 return Pair.make(input, out); 491 } 492 493 /* 494 * (non-Javadoc) 495 * @see net.automatalib.graphs.IndefiniteGraph#getOutgoingEdges(java.lang.Object) 496 */ 497 @Override 498 public Collection<TransitionRecord> getOutgoingEdges(State node) { 499 List<TransitionRecord> edges = new ArrayList<TransitionRecord>(); 500 for(int i = 0; i < alphabetSize; i++) { 501 if(node.getSuccessor(i) != null) 502 edges.add(new TransitionRecord(node, i)); 503 } 504 return edges; 505 } 506 507 /* 508 * (non-Javadoc) 509 * @see net.automatalib.graphs.IndefiniteGraph#getTarget(java.lang.Object) 510 */ 511 @Override 512 public State getTarget(TransitionRecord edge) { 513 return edge.source.getSuccessor(edge.transIdx); 514 } 515 516 /* 517 * (non-Javadoc) 518 * @see net.automatalib.automata.concepts.TransitionOutput#getTransitionOutput(java.lang.Object) 519 */ 520 @Override 521 @SuppressWarnings("unchecked") 522 public O getTransitionOutput(TransitionRecord transition) { 523 return (O)transition.source.getOutput(transition.transIdx); 524 } 525 526 /* 527 * (non-Javadoc) 528 * @see net.automatalib.ts.DeterministicTransitionSystem#getTransition(java.lang.Object, java.lang.Object) 529 */ 530 @Override 531 public TransitionRecord getTransition(State state, I input) { 532 int idx = inputAlphabet.getSymbolIndex(input); 533 if(state.getSuccessor(idx) != null) 534 return new TransitionRecord(state, idx); 535 return null; 536 } 537 538 /* 539 * (non-Javadoc) 540 * @see net.automatalib.graphs.dot.DOTPlottableGraph#getHelper() 541 */ 542 @Override 543 public GraphDOTHelper<State, TransitionRecord> getGraphDOTHelper() { 544 return new DOTHelper(inputAlphabet, init); 545 } 546 547 /* 548 * (non-Javadoc) 549 * @see net.automatalib.ts.SimpleFiniteTS#getStates() 550 */ 551 @Override 552 public Collection<State> getStates() { 553 return Collections.unmodifiableCollection(register.values()); 554 } 555 556 /* 557 * (non-Javadoc) 558 * @see net.automatalib.graphs.FiniteGraph#getNodes() 559 */ 560 @Override 561 public Collection<State> getNodes() { 562 return Collections.unmodifiableCollection(register.values()); 563 } 564 565 /* 566 * (non-Javadoc) 567 * @see net.automatalib.incremental.IncrementalConstruction#getInputAlphabet() 568 */ 569 @Override 570 public Alphabet<I> getInputAlphabet() { 571 return inputAlphabet; 572 } 573 574 /* 575 * (non-Javadoc) 576 * @see net.automatalib.incremental.IncrementalConstruction#findSeparatingWord(java.lang.Object, java.util.Collection, boolean) 577 */ 578 @Override 579 public Word<I> findSeparatingWord(MealyMachine<?, I, ?, O> target, 580 Collection<? extends I> inputs, boolean omitUndefined) { 581 return doFindSeparatingWord(target, inputs, omitUndefined); 582 } 583 584 /* 585 * (non-Javadoc) 586 * @see net.automatalib.incremental.IncrementalConstruction#toAutomaton() 587 */ 588 @Override 589 @SuppressWarnings("unchecked") 590 public CompactMealy<I, O> toAutomaton() { 591 CompactMealy<I,O> result = new CompactMealy<I,O>(inputAlphabet, register.size()); 592 593 Map<State,Integer> stateMap = new HashMap<>(); 594 595 for(State s : register.values()) { 596 Integer id; 597 if(s == init) 598 id = result.addInitialState(); 599 else 600 id = result.addState(); 601 stateMap.put(s, id); 602 } 603 604 for(Map.Entry<State,Integer> e : stateMap.entrySet()) { 605 State s = e.getKey(); 606 Integer id = e.getValue(); 607 608 for(int i = 0; i < alphabetSize; i++) { 609 State succ = s.getSuccessor(i); 610 if(succ == null) 611 continue; 612 I sym = inputAlphabet.getSymbol(i); 613 O out = (O)s.getOutput(i); 614 Integer succId = stateMap.get(succ); 615 result.addTransition(id, sym, succId, out); 616 } 617 } 618 619 return result; 620 } 621 622 /* 623 * (non-Javadoc) 624 * @see net.automatalib.incremental.IncrementalConstruction#hasDefinitiveInformation(net.automatalib.words.Word) 625 */ 626 @Override 627 public boolean hasDefinitiveInformation(Word<I> word) { 628 State s = getState(word); 629 return (s != null); 630 } 631 632 /////////////////////////////////////////////////////////////////////// 633 // Equivalence test // 634 /////////////////////////////////////////////////////////////////////// 635 636 private static int getStateId(State state, Map<State,Integer> ids) { 637 Integer id = ids.get(state); 638 if(id == null) { 639 id = ids.size(); 640 ids.put(state, id); 641 } 642 return id.intValue(); 643 } 644 645 private static final class Record<S,I> { 646 private final State state1; 647 private final S state2; 648 private final I reachedVia; 649 private final Record<S,I> reachedFrom; 650 private final int depth; 651 652 public Record(State state1, S state2, Record<S,I> reachedFrom, I reachedVia) { 653 this.state1 = state1; 654 this.state2 = state2; 655 this.reachedFrom = reachedFrom; 656 this.reachedVia = reachedVia; 657 this.depth = (reachedFrom != null) ? reachedFrom.depth + 1 : 0; 658 } 659 660 public Record(State state1, S state2) { 661 this(state1, state2, null, null); 662 } 663 } 664 665 private <S,T> Word<I> doFindSeparatingWord(MealyMachine<S,I,T,O> mealy, Collection<? extends I> inputs, boolean omitUndefined) { 666 int thisStates = register.size(); 667 668 UnionFind uf = new UnionFind(thisStates + mealy.size()); 669 670 Map<State,Integer> ids = new HashMap<State,Integer>(); 671 672 State init1 = init; 673 S init2 = mealy.getInitialState(); 674 675 if(init2 == null) 676 return omitUndefined ? null : Word.<I>epsilon(); 677 678 StateIDs<S> mealyIds = mealy.stateIDs(); 679 680 int id1 = getStateId(init1, ids), id2 = mealyIds.getStateId(init2) + thisStates; 681 682 uf.link(id1, id2); 683 684 Queue<Record<S,I>> queue = new ArrayDeque<Record<S,I>>(); 685 686 queue.offer(new Record<S,I>(init1, init2)); 687 688 I lastSym = null; 689 690 Record<S,I> current; 691 692explore:while((current = queue.poll()) != null) { 693 State state1 = current.state1; 694 S state2 = current.state2; 695 696 for(I sym : inputs) { 697 int idx = inputAlphabet.getSymbolIndex(sym); 698 State succ1 = state1.getSuccessor(idx); 699 if(succ1 == null) 700 continue; 701 702 T trans2 = mealy.getTransition(state2, sym); 703 if(trans2 == null) { 704 if(omitUndefined) 705 continue; 706 lastSym = sym; 707 break explore; 708 } 709 710 Object out1 = state1.getOutput(idx); 711 Object out2 = mealy.getTransitionOutput(trans2); 712 if(!Objects.equals(out1, out2)) { 713 lastSym = sym; 714 break explore; 715 } 716 717 S succ2 = mealy.getSuccessor(trans2); 718 719 id1 = getStateId(succ1, ids); 720 id2 = mealyIds.getStateId(succ2) + thisStates; 721 722 int r1 = uf.find(id1), r2 = uf.find(id2); 723 724 if(r1 == r2) 725 continue; 726 727 uf.link(r1, r2); 728 729 queue.offer(new Record<>(succ1, succ2, current, sym)); 730 } 731 } 732 733 if(current == null) 734 return null; 735 736 int ceLength = current.depth; 737 if(lastSym != null) 738 ceLength++; 739 740 WordBuilder<I> wb = new WordBuilder<I>(null, ceLength); 741 742 int index = ceLength; 743 744 if(lastSym != null) 745 wb.setSymbol(--index, lastSym); 746 747 while(current.reachedFrom != null) { 748 wb.setSymbol(--index, current.reachedVia); 749 current = current.reachedFrom; 750 } 751 752 return wb.toWord(); 753 } 754 755 @Override 756 public NodeIDs<State> nodeIDs() { 757 return AbstractAutomatonGraph.nodeIDs(this); 758 } 759 760 @Override 761 public <V> MutableMapping<State, V> createStaticNodeMapping() { 762 return AbstractAutomatonGraph.createDynamicNodeMapping(this); 763 } 764 765 @Override 766 public <V> MutableMapping<State, V> createDynamicNodeMapping() { 767 return AbstractAutomatonGraph.createDynamicNodeMapping(this); 768 } 769 770 771}