Skip to content

Commit 2290f46

Browse files
committed
optimized cycle detection and lambda based DFS
1 parent 6a83122 commit 2290f46

File tree

3 files changed

+180
-45
lines changed

3 files changed

+180
-45
lines changed

include/maxplus/base/fsm/fsm.h

Lines changed: 112 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@
4444
#include "maxplus/base/basic_types.h"
4545
#include "maxplus/base/exception/exception.h"
4646
#include "maxplus/base/string/cstring.h"
47+
#include <functional>
4748
#include <list>
4849
#include <map>
4950
#include <memory>
5051
#include <set>
52+
#include <utility>
5153

5254
namespace FSM {
5355

@@ -251,8 +253,9 @@ class DepthFirstSearch {
251253
SetOfEdgeRefs::CIter iter;
252254
};
253255

254-
protected:
255256
using DfsStack = std::list<DFSStackItem>;
257+
258+
protected:
256259
DfsStack dfsStack;
257260

258261
public:
@@ -267,13 +270,9 @@ class DepthFirstSearch {
267270

268271
virtual void onSimpleCycle(DfsStack &stack){};
269272

270-
const FiniteStateMachine& getFSM(){
271-
return this->fsm;
272-
}
273+
const FiniteStateMachine &getFSM() { return this->fsm; }
273274

274-
explicit DepthFirstSearch(const FiniteStateMachine &targetFsm) :
275-
fsm(targetFsm)
276-
{};
275+
explicit DepthFirstSearch(const FiniteStateMachine &targetFsm) : fsm(targetFsm){};
277276

278277
// Execute the depth first search
279278
void DoDepthFirstSearch(const SetOfStateRefs &startingStates, bool fullDFS = false) {
@@ -283,48 +282,69 @@ class DepthFirstSearch {
283282

284283
this->_abort = false;
285284

286-
// put initial state on the stack
287-
for (const auto& s: startingStates) {
285+
// for each of the starting states
286+
auto nextStartingState = startingStates.begin();
287+
while (nextStartingState != startingStates.end()) {
288+
289+
// skip states we have already visited
290+
while (nextStartingState != startingStates.end()
291+
&& visitedStates.includesState(*nextStartingState)) {
292+
nextStartingState++;
293+
}
294+
// if we did not find any state anymore
295+
if (nextStartingState == startingStates.end()) {
296+
break;
297+
}
298+
299+
statesOnStack.clear();
300+
StateRef s = *nextStartingState;
288301
dfsStack.emplace_back(s);
302+
statesOnStack.insert(s);
303+
visitedStates.insert(s);
289304
this->onEnterState(s);
290-
}
291305

292-
while (!this->_abort && !(dfsStack.empty())) {
293-
DFSStackItem &si = dfsStack.back();
294-
295-
// current item complete?
296-
if (si.atEnd()) {
297-
// pop it from stack
298-
this->onLeaveState(si.getState());
299-
const auto *const s = si.getState();
300-
statesOnStack.erase(s);
301-
if (fullDFS) {
302-
assert(visitedStates.includesState(s));
303-
visitedStates.erase(s);
306+
while (!this->_abort && !(dfsStack.empty())) {
307+
DFSStackItem &si = dfsStack.back();
308+
if (!visitedStates.includesState(si.getState())) {
309+
this->onEnterState(si.getState());
310+
visitedStates.insert(si.getState());
304311
}
305-
dfsStack.pop_back();
306-
} else {
307-
// goto next edge
308-
const auto *e = *(si.getIter());
309-
si.advance();
310-
StateRef dest = e->getDestination();
311-
bool revisit = statesOnStack.includesState(dest);
312-
if (revisit) {
313-
// cycle found
314-
this->onSimpleCycle(dfsStack);
312+
// current item complete?
313+
if (si.atEnd()) {
314+
// pop it from stack
315+
this->onLeaveState(si.getState());
316+
const auto *const s = si.getState();
317+
statesOnStack.erase(s);
318+
if (fullDFS) {
319+
assert(visitedStates.includesState(s));
320+
visitedStates.erase(s);
321+
}
322+
dfsStack.pop_back();
315323
} else {
316-
// if target state not visited before
317-
dfsStack.emplace_back(dest);
318-
this->onTransition(*e);
319-
this->onEnterState(dest);
320-
visitedStates.insert(dest);
321-
statesOnStack.insert(dest);
324+
// goto next edge
325+
const auto *e = *(si.getIter());
326+
si.advance();
327+
StateRef dest = e->getDestination();
328+
bool revisit = statesOnStack.includesState(dest);
329+
if (revisit) {
330+
// cycle found
331+
this->onSimpleCycle(dfsStack);
332+
} else {
333+
if (!visitedStates.includesState(dest)) {
334+
// if target state not visited before
335+
dfsStack.emplace_back(dest);
336+
this->onTransition(*e);
337+
this->onEnterState(dest);
338+
visitedStates.insert(dest);
339+
statesOnStack.insert(dest);
340+
}
341+
}
322342
}
323343
}
324344
}
325345
}
326346

327-
void DoDepthFirstSearch(const StateRef &startingState, bool fullDFS = false){
347+
void DoDepthFirstSearch(const StateRef &startingState, bool fullDFS = false) {
328348
SetOfStateRefs stateSet;
329349
stateSet.insert(startingState);
330350
return this->DoDepthFirstSearch(stateSet, fullDFS);
@@ -335,23 +355,70 @@ class DepthFirstSearch {
335355
this->DoDepthFirstSearch(this->fsm.getInitialStates(), fullDFS);
336356
}
337357

338-
void abortDFS() {
339-
this->_abort = true;
340-
}
358+
void abortDFS() { this->_abort = true; }
341359

342360
private:
343361
const FiniteStateMachine &fsm;
344362
bool _abort{};
363+
};
364+
365+
class DepthFirstSearchLambda : public DepthFirstSearch {
366+
367+
public:
368+
DepthFirstSearchLambda(const DepthFirstSearchLambda &) = delete;
369+
DepthFirstSearchLambda &operator=(const DepthFirstSearchLambda &other) = delete;
370+
DepthFirstSearchLambda(DepthFirstSearch &&) = delete;
371+
DepthFirstSearchLambda &operator=(DepthFirstSearchLambda &&) = delete;
372+
373+
private:
374+
using TOnEnterLambda = std::function<void(StateRef s)>;
375+
TOnEnterLambda _onEnterStateLambda;
376+
using TOnLeaveLambda = std::function<void(StateRef s)>;
377+
TOnLeaveLambda _onLeaveStateLambda;
378+
using TOnTransitionLambda = std::function<void(const Edge &e)>;
379+
TOnTransitionLambda _onTransitionLambda;
380+
using TOnSimpleCycleLambda = std::function<void(const DepthFirstSearch::DfsStack &stack)>;
381+
TOnSimpleCycleLambda _onSimpleCycleLambda;
382+
383+
public:
384+
~DepthFirstSearchLambda() override = default;
345385

386+
void onEnterState(StateRef s) override { this->_onEnterStateLambda(s); };
387+
388+
void onLeaveState(StateRef s) override { this->_onLeaveStateLambda(s); };
389+
390+
void onTransition(const Edge &e) override { this->_onTransitionLambda(e); };
391+
392+
void onSimpleCycle(DepthFirstSearch::DfsStack &stack) override {
393+
this->_onSimpleCycleLambda(stack);
394+
};
395+
396+
explicit DepthFirstSearchLambda(const FiniteStateMachine &targetFsm) :
397+
_onEnterStateLambda([](StateRef) {}),
398+
_onLeaveStateLambda([](StateRef) {}),
399+
_onTransitionLambda([](const Edge &) {}),
400+
_onSimpleCycleLambda([](const DepthFirstSearch::DfsStack &) {}),
401+
DepthFirstSearch(targetFsm){};
402+
403+
void setOnEnterLambda(TOnEnterLambda lambda) { this->_onEnterStateLambda = std::move(lambda); }
404+
405+
void setOnLeaveLambda(TOnLeaveLambda lambda) { this->_onLeaveStateLambda = std::move(lambda); }
406+
407+
void setOnTransitionLambda(TOnTransitionLambda lambda) {
408+
this->_onTransitionLambda = std::move(lambda);
409+
}
410+
411+
void setOnSimpleCycleLambda(TOnSimpleCycleLambda lambda) {
412+
this->_onSimpleCycleLambda = std::move(lambda);
413+
}
346414
};
347415

348416
// Check for cycles
349417
class DetectCycle : public DepthFirstSearch {
350418
public:
351419
bool hasCycle = false;
352420

353-
explicit DetectCycle(const FiniteStateMachine &targetFsm) : DepthFirstSearch(targetFsm){
354-
};
421+
explicit DetectCycle(const FiniteStateMachine &targetFsm) : DepthFirstSearch(targetFsm){};
355422

356423
~DetectCycle() override = default;
357424

@@ -669,7 +736,7 @@ class FiniteStateMachine : public Abstract::FiniteStateMachine {
669736

670737
[[nodiscard]] FSM::Abstract::SetOfStateRefs getStateRefs() const override {
671738
FSM::Abstract::SetOfStateRefs result;
672-
for (const auto& s: this->states) {
739+
for (const auto &s : this->states) {
673740
result.insert(s.second->getReference());
674741
}
675742
return result;

src/testbench/graph/mpautomatontest.cc

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ void MPAutomatonTest::Run() {
2222
testCreateFSM();
2323
testDeterminizeFSM();
2424
testMinimizeFSM();
25+
testDFSFSM();
2526
testDetectCycleFSM();
2627
}
2728

@@ -128,6 +129,72 @@ void MPAutomatonTest::testMinimizeFSM() {
128129
ASSERT_EQUAL(fsaMin->getEdges().size(), 2);
129130
}
130131

132+
void MPAutomatonTest::testDFSFSM() {
133+
134+
std::cout << "Running test: Depth-First Search FSM\n";
135+
136+
{
137+
FSM::Labeled::FiniteStateMachine<int, int> fsa;
138+
139+
const auto *s0 = fsa.addState(0);
140+
const auto *s1 = fsa.addState(1);
141+
const auto *s2 = fsa.addState(2);
142+
const auto *s3 = fsa.addState(3);
143+
const auto *s4 = fsa.addState(4);
144+
145+
fsa.setInitialState(*s0);
146+
fsa.addInitialState(*s2);
147+
148+
fsa.addEdge(*s0, 2, *s1);
149+
fsa.addEdge(*s1, 2, *s1);
150+
fsa.addEdge(*s2, 2, *s1);
151+
fsa.addEdge(*s2, 2, *s3);
152+
fsa.addEdge(*s3, 2, *s2);
153+
fsa.addEdge(*s4, 2, *s3);
154+
155+
bool foundCycle = false;
156+
FSM::Abstract::SetOfStateRefs statesFound;
157+
158+
FSM::Abstract::DepthFirstSearchLambda DFS(fsa);
159+
160+
DFS.setOnEnterLambda([&statesFound](FSM::Abstract::StateRef s) {
161+
ASSERT(!statesFound.includesState(s));
162+
statesFound.insert(s);
163+
});
164+
DFS.setOnSimpleCycleLambda(
165+
[&foundCycle](const FSM::Abstract::DepthFirstSearch::DfsStack &) {
166+
foundCycle = true;
167+
});
168+
169+
// from all initial states
170+
171+
DFS.DoDepthFirstSearch();
172+
ASSERT(foundCycle);
173+
ASSERT(statesFound.size() == 4);
174+
}
175+
176+
{
177+
FSM::Labeled::FiniteStateMachine<int, int> fsa;
178+
179+
const auto *s2 = fsa.addState(5);
180+
const auto *s3 = fsa.addState(4);
181+
const auto *s0 = fsa.addState(3);
182+
const auto *s1 = fsa.addState(5);
183+
184+
fsa.addEdge(*s0, 2, *s1);
185+
fsa.addEdge(*s1, 2, *s2);
186+
fsa.addEdge(*s2, 2, *s3);
187+
auto e = fsa.addEdge(*s3, 2, *s1);
188+
189+
fsa.setEdgeLabel(e, 5);
190+
191+
FSM::Abstract::DetectCycle DC(fsa);
192+
bool hasCycle = DC.checkForCycles();
193+
194+
ASSERT(hasCycle);
195+
}
196+
}
197+
131198
void MPAutomatonTest::testDetectCycleFSM() {
132199

133200
std::cout << "Running test: DetectCycles\n";

src/testbench/graph/mpautomatontest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ class MPAutomatonTest : public ::testing::Test {
1919
void testDeterminizeFSM();
2020
void testMinimizeFSM();
2121
void testDetectCycleFSM();
22+
void testDFSFSM();
2223
};

0 commit comments

Comments
 (0)