1 | <?php |
||
2 | |||
3 | namespace Smoren\GraphTools\Traverse; |
||
4 | |||
5 | use Ds\Collection; |
||
6 | use Ds\Queue; |
||
7 | use Ds\Stack; |
||
8 | use Generator; |
||
9 | use Smoren\GraphTools\Traverse\Interfaces\TraverseInterface; |
||
10 | use Smoren\GraphTools\Conditions\Interfaces\FilterConditionInterface; |
||
11 | use Smoren\GraphTools\Filters\Interfaces\TraverseFilterInterface; |
||
12 | use Smoren\GraphTools\Models\Interfaces\EdgeInterface; |
||
13 | use Smoren\GraphTools\Models\Interfaces\VertexInterface; |
||
14 | use Smoren\GraphTools\Store\Interfaces\GraphRepositoryInterface; |
||
15 | use Smoren\GraphTools\Structs\Interfaces\TraverseBranchContextInterface; |
||
16 | use Smoren\GraphTools\Structs\Interfaces\TraverseContextInterface; |
||
17 | use Smoren\GraphTools\Structs\Interfaces\TraverseStepIteratorInterface; |
||
18 | use Smoren\GraphTools\Structs\TraverseBranchContext; |
||
19 | use Smoren\GraphTools\Structs\TraverseContext; |
||
20 | use Smoren\GraphTools\Structs\TraverseStepIterator; |
||
21 | use Smoren\GraphTools\Structs\TraverseStepItem; |
||
22 | |||
23 | /** |
||
24 | * Class for non-directional graph traversing |
||
25 | * @author Smoren <[email protected]> |
||
26 | */ |
||
27 | class Traverse implements TraverseInterface |
||
28 | { |
||
29 | /** |
||
30 | * Stop branch command |
||
31 | */ |
||
32 | public const STOP_BRANCH = 1; |
||
33 | /** |
||
34 | * Stop all branches command |
||
35 | */ |
||
36 | public const STOP_ALL = 2; |
||
37 | |||
38 | /** |
||
39 | * Wide traverse mode |
||
40 | */ |
||
41 | public const MODE_WIDE = 1; |
||
42 | /** |
||
43 | * Deep traverse mode |
||
44 | */ |
||
45 | public const MODE_DEEP = 2; |
||
46 | |||
47 | /** |
||
48 | * @var GraphRepositoryInterface |
||
49 | */ |
||
50 | protected GraphRepositoryInterface $repository; |
||
51 | |||
52 | /** |
||
53 | * @param GraphRepositoryInterface $repository |
||
54 | */ |
||
55 | public function __construct(GraphRepositoryInterface $repository) |
||
56 | { |
||
57 | $this->repository = $repository; |
||
58 | } |
||
59 | |||
60 | /** |
||
61 | * @inheritDoc |
||
62 | * @return Generator<TraverseContextInterface> |
||
63 | */ |
||
64 | public function generate( |
||
65 | VertexInterface $start, |
||
66 | TraverseFilterInterface $filter, |
||
67 | int $traverseMode = self::MODE_WIDE |
||
68 | ): Generator { |
||
69 | $branchContext = $this->createBranchContext(0, null, $start); |
||
70 | $globalPassedVertexesMap = []; |
||
71 | $context = $this->createContext($start, null, $branchContext, [], $globalPassedVertexesMap); |
||
72 | yield from $this->traverse($context, $filter, $traverseMode); |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Graph traverse generator |
||
77 | * @param TraverseContextInterface $startContext traverse context of the first vertex |
||
78 | * @param TraverseFilterInterface $filter traverse filter |
||
79 | * @param int $traverseMode traverse mode (wide or deep) |
||
80 | * @return Generator<TraverseContextInterface> |
||
81 | */ |
||
82 | protected function traverse( |
||
83 | TraverseContextInterface $startContext, |
||
84 | TraverseFilterInterface $filter, |
||
85 | int $traverseMode |
||
86 | ): Generator { |
||
87 | $lastBranchIndex = $startContext->getBranchContext()->getIndex(); |
||
88 | $globalPassedVertexesMap = $startContext->getGlobalPassedVertexesMap(); |
||
89 | $contexts = $this->createContextCollection($traverseMode, $startContext); |
||
90 | |||
91 | while(count($contexts)) { |
||
92 | /** @var TraverseContextInterface $currentContext */ |
||
93 | $currentContext = $contexts->pop(); |
||
94 | $currentVertex = $currentContext->getVertex(); |
||
95 | |||
96 | $customPassCondition = null; |
||
97 | $nextVertex = null; |
||
98 | if($filter->matchesHandleCondition($currentContext)) { |
||
99 | $cmd = (yield $currentContext); |
||
100 | |||
101 | if($cmd instanceof FilterConditionInterface) { |
||
102 | $customPassCondition = $cmd; |
||
103 | yield $currentContext; |
||
104 | } elseif($cmd instanceof VertexInterface) { |
||
105 | $nextVertex = $cmd; |
||
106 | yield $currentContext; |
||
107 | } else { |
||
108 | switch($cmd) { |
||
109 | case static::STOP_BRANCH: |
||
110 | yield $currentContext; |
||
111 | continue 2; |
||
112 | case static::STOP_ALL: |
||
113 | return; |
||
114 | } |
||
115 | } |
||
116 | } |
||
117 | |||
118 | $passedVertexesMap = $currentContext->getPassedVertexesMap(); |
||
119 | $passedVertexesMap[$currentVertex->getId()] = $currentVertex; |
||
120 | $globalPassedVertexesMap[$currentVertex->getId()] = $currentVertex; |
||
121 | |||
122 | if ($nextVertex !== null) { |
||
123 | $nextVertexes = new TraverseStepIterator([new TraverseStepItem(null, $nextVertex)]); |
||
124 | } else { |
||
125 | $customPassCondition = $customPassCondition ?? $filter->getPassCondition($currentContext); |
||
126 | $nextVertexes = $this->getNextVertexes($currentVertex, $customPassCondition); |
||
127 | } |
||
128 | |||
129 | $i = 0; |
||
130 | foreach($nextVertexes as $edge => $vertex) { |
||
131 | $currentBranchContext = $currentContext->getBranchContext(); |
||
132 | if(count($nextVertexes) > 1 && $i > 0) { |
||
133 | $nextBranchContext = $this->createBranchContext( |
||
134 | ++$lastBranchIndex, |
||
135 | $currentBranchContext->getIndex(), |
||
136 | $currentVertex |
||
137 | ); |
||
138 | } else { |
||
139 | $nextBranchContext = $currentBranchContext; |
||
140 | } |
||
141 | |||
142 | $contexts->push($this->createContext( |
||
143 | $vertex, |
||
144 | $edge, |
||
145 | $nextBranchContext, |
||
146 | $passedVertexesMap, |
||
147 | $globalPassedVertexesMap |
||
148 | )); |
||
149 | |||
150 | ++$i; |
||
151 | } |
||
152 | } |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * Returns traverse iterator for the next step of traversing |
||
157 | * @param VertexInterface $vertex vertex to traverse from |
||
158 | * @param FilterConditionInterface $condition pass condition |
||
159 | * @return TraverseStepIteratorInterface next vertexes iterator |
||
160 | */ |
||
161 | protected function getNextVertexes( |
||
162 | VertexInterface $vertex, |
||
163 | FilterConditionInterface $condition |
||
164 | ): TraverseStepIteratorInterface { |
||
165 | return TraverseStepIterator::combine( |
||
166 | $this->repository->getNextVertexes($vertex, $condition), |
||
167 | $this->repository->getPrevVertexes($vertex, $condition) |
||
168 | ); |
||
169 | } |
||
170 | |||
171 | /** |
||
172 | * Creates context collection |
||
173 | * @param int $traverseMode traverse mode |
||
174 | * @param TraverseContextInterface $startContext start context |
||
175 | * @return Queue<TraverseContextInterface>|Stack<TraverseContextInterface> |
||
176 | */ |
||
177 | protected function createContextCollection( |
||
178 | int $traverseMode, |
||
179 | TraverseContextInterface $startContext |
||
180 | ): Collection { |
||
181 | if($traverseMode === self::MODE_WIDE) { |
||
182 | $contexts = new Queue([$startContext]); |
||
183 | } else { |
||
184 | $contexts = new Stack([$startContext]); |
||
185 | } |
||
186 | |||
187 | return $contexts; |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
188 | } |
||
189 | |||
190 | /** |
||
191 | * Creates new traverse context instance |
||
192 | * @param VertexInterface $vertex current vertex |
||
193 | * @param EdgeInterface|null $edge current edge leading to current vertex |
||
194 | * @param TraverseBranchContextInterface $branchContext branch context |
||
195 | * @param array<VertexInterface> $passedVertexesMap map of passed vertexes in current branch |
||
196 | * @param array<VertexInterface> $globalPassedVertexesMap map of all the passed vertexes |
||
197 | * @return TraverseContextInterface traverse context |
||
198 | */ |
||
199 | protected function createContext( |
||
200 | VertexInterface $vertex, |
||
201 | ?EdgeInterface $edge, |
||
202 | TraverseBranchContextInterface $branchContext, |
||
203 | array $passedVertexesMap, |
||
204 | array &$globalPassedVertexesMap |
||
205 | ): TraverseContextInterface { |
||
206 | return new TraverseContext( |
||
207 | $vertex, |
||
208 | $edge, |
||
209 | $this->repository, |
||
210 | $branchContext, |
||
211 | $passedVertexesMap, |
||
212 | $globalPassedVertexesMap |
||
213 | ); |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Creates new branch context instance |
||
218 | * @param int $index branch index |
||
219 | * @param int|null $parentIndex parent branch index |
||
220 | * @param VertexInterface $start vertex which started this branch |
||
221 | * @return TraverseBranchContextInterface new branch context |
||
222 | */ |
||
223 | protected function createBranchContext( |
||
224 | int $index, |
||
225 | ?int $parentIndex, |
||
226 | VertexInterface $start |
||
227 | ): TraverseBranchContextInterface { |
||
228 | return new TraverseBranchContext($index, $parentIndex, $start); |
||
229 | } |
||
230 | } |
||
231 |