summaryrefslogtreecommitdiff
path: root/vendor/doctrine/orm/src/Internal/StronglyConnectedComponents.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/doctrine/orm/src/Internal/StronglyConnectedComponents.php')
-rw-r--r--vendor/doctrine/orm/src/Internal/StronglyConnectedComponents.php159
1 files changed, 159 insertions, 0 deletions
diff --git a/vendor/doctrine/orm/src/Internal/StronglyConnectedComponents.php b/vendor/doctrine/orm/src/Internal/StronglyConnectedComponents.php
new file mode 100644
index 0000000..dd4fc98
--- /dev/null
+++ b/vendor/doctrine/orm/src/Internal/StronglyConnectedComponents.php
@@ -0,0 +1,159 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Doctrine\ORM\Internal;
6
7use InvalidArgumentException;
8
9use function array_keys;
10use function array_pop;
11use function array_push;
12use function min;
13use function spl_object_id;
14
15/**
16 * StronglyConnectedComponents implements Tarjan's algorithm to find strongly connected
17 * components (SCC) in a directed graph. This algorithm has a linear running time based on
18 * nodes (V) and edges between the nodes (E), resulting in a computational complexity
19 * of O(V + E).
20 *
21 * See https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
22 * for an explanation and the meaning of the DFS and lowlink numbers.
23 *
24 * @internal
25 */
26final class StronglyConnectedComponents
27{
28 private const NOT_VISITED = 1;
29 private const IN_PROGRESS = 2;
30 private const VISITED = 3;
31
32 /**
33 * Array of all nodes, indexed by object ids.
34 *
35 * @var array<int, object>
36 */
37 private array $nodes = [];
38
39 /**
40 * DFS state for the different nodes, indexed by node object id and using one of
41 * this class' constants as value.
42 *
43 * @var array<int, self::*>
44 */
45 private array $states = [];
46
47 /**
48 * Edges between the nodes. The first-level key is the object id of the outgoing
49 * node; the second array maps the destination node by object id as key.
50 *
51 * @var array<int, array<int, bool>>
52 */
53 private array $edges = [];
54
55 /**
56 * DFS numbers, by object ID
57 *
58 * @var array<int, int>
59 */
60 private array $dfs = [];
61
62 /**
63 * lowlink numbers, by object ID
64 *
65 * @var array<int, int>
66 */
67 private array $lowlink = [];
68
69 private int $maxdfs = 0;
70
71 /**
72 * Nodes representing the SCC another node is in, indexed by lookup-node object ID
73 *
74 * @var array<int, object>
75 */
76 private array $representingNodes = [];
77
78 /**
79 * Stack with OIDs of nodes visited in the current state of the DFS
80 *
81 * @var list<int>
82 */
83 private array $stack = [];
84
85 public function addNode(object $node): void
86 {
87 $id = spl_object_id($node);
88 $this->nodes[$id] = $node;
89 $this->states[$id] = self::NOT_VISITED;
90 $this->edges[$id] = [];
91 }
92
93 public function hasNode(object $node): bool
94 {
95 return isset($this->nodes[spl_object_id($node)]);
96 }
97
98 /**
99 * Adds a new edge between two nodes to the graph
100 */
101 public function addEdge(object $from, object $to): void
102 {
103 $fromId = spl_object_id($from);
104 $toId = spl_object_id($to);
105
106 $this->edges[$fromId][$toId] = true;
107 }
108
109 public function findStronglyConnectedComponents(): void
110 {
111 foreach (array_keys($this->nodes) as $oid) {
112 if ($this->states[$oid] === self::NOT_VISITED) {
113 $this->tarjan($oid);
114 }
115 }
116 }
117
118 private function tarjan(int $oid): void
119 {
120 $this->dfs[$oid] = $this->lowlink[$oid] = $this->maxdfs++;
121 $this->states[$oid] = self::IN_PROGRESS;
122 array_push($this->stack, $oid);
123
124 foreach ($this->edges[$oid] as $adjacentId => $ignored) {
125 if ($this->states[$adjacentId] === self::NOT_VISITED) {
126 $this->tarjan($adjacentId);
127 $this->lowlink[$oid] = min($this->lowlink[$oid], $this->lowlink[$adjacentId]);
128 } elseif ($this->states[$adjacentId] === self::IN_PROGRESS) {
129 $this->lowlink[$oid] = min($this->lowlink[$oid], $this->dfs[$adjacentId]);
130 }
131 }
132
133 $lowlink = $this->lowlink[$oid];
134 if ($lowlink === $this->dfs[$oid]) {
135 $representingNode = null;
136 do {
137 $unwindOid = array_pop($this->stack);
138
139 if (! $representingNode) {
140 $representingNode = $this->nodes[$unwindOid];
141 }
142
143 $this->representingNodes[$unwindOid] = $representingNode;
144 $this->states[$unwindOid] = self::VISITED;
145 } while ($unwindOid !== $oid);
146 }
147 }
148
149 public function getNodeRepresentingStronglyConnectedComponent(object $node): object
150 {
151 $oid = spl_object_id($node);
152
153 if (! isset($this->representingNodes[$oid])) {
154 throw new InvalidArgumentException('unknown node');
155 }
156
157 return $this->representingNodes[$oid];
158 }
159}