diff options
Diffstat (limited to 'vendor/doctrine/orm/src/Internal/StronglyConnectedComponents.php')
| -rw-r--r-- | vendor/doctrine/orm/src/Internal/StronglyConnectedComponents.php | 159 |
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 | |||
| 3 | declare(strict_types=1); | ||
| 4 | |||
| 5 | namespace Doctrine\ORM\Internal; | ||
| 6 | |||
| 7 | use InvalidArgumentException; | ||
| 8 | |||
| 9 | use function array_keys; | ||
| 10 | use function array_pop; | ||
| 11 | use function array_push; | ||
| 12 | use function min; | ||
| 13 | use 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 | */ | ||
| 26 | final 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 | } | ||
