templesofcode /
nodes-and-edges
| 1 | <?php |
||
| 2 | |||
| 3 | namespace TemplesOfCode\NodesAndEdges; |
||
| 4 | |||
| 5 | use InvalidArgumentException; |
||
| 6 | |||
| 7 | /** |
||
| 8 | * Class Graph |
||
| 9 | * @package TemplesOfCode\NodesAndEdges |
||
| 10 | */ |
||
| 11 | abstract class Graph |
||
| 12 | { |
||
| 13 | /** |
||
| 14 | * @var int |
||
| 15 | */ |
||
| 16 | protected $vertices; |
||
| 17 | |||
| 18 | /** |
||
| 19 | * @var int |
||
| 20 | */ |
||
| 21 | protected $edges; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * @var array |
||
| 25 | */ |
||
| 26 | protected $adjacencyList; |
||
| 27 | |||
| 28 | /** |
||
| 29 | * Initializes an empty edge-weighted graph with {@code V} vertices and 0 edges. |
||
| 30 | * |
||
| 31 | * @param int $vertices |
||
| 32 | * @param array|null $adjacencyList |
||
| 33 | */ |
||
| 34 | public function __construct(int $vertices, array $adjacencyList = null) |
||
| 35 | { |
||
| 36 | // |
||
| 37 | if ($vertices < 0) { |
||
| 38 | throw new InvalidArgumentException( |
||
| 39 | 'Number of vertices must be non-negative' |
||
| 40 | ); |
||
| 41 | } |
||
| 42 | // set |
||
| 43 | $this->vertices = $vertices; |
||
| 44 | // init |
||
| 45 | $this->edges = 0; |
||
| 46 | // get the ne |
||
| 47 | if (!empty($adjacencyList)) { |
||
| 48 | // set it |
||
| 49 | $this->adjacencyList= $adjacencyList; |
||
| 50 | } else { |
||
| 51 | // init |
||
| 52 | $this->adjacencyList = []; |
||
| 53 | // iterate over the set of vertices |
||
| 54 | for ($vertex = 0; $vertex < $vertices; $vertex++) { |
||
| 55 | // initialize each vertex adjacency list |
||
| 56 | $this->adjacencyList[$vertex] = []; |
||
| 57 | } |
||
| 58 | } |
||
| 59 | } |
||
| 60 | |||
| 61 | /** |
||
| 62 | * Returns the number of vertices in this graph. |
||
| 63 | * |
||
| 64 | * @return int |
||
| 65 | */ |
||
| 66 | public function getVertices() |
||
| 67 | { |
||
| 68 | // return the amount |
||
| 69 | return $this->vertices; |
||
| 70 | } |
||
| 71 | |||
| 72 | /** |
||
| 73 | * Returns the number of edges in this graph. |
||
| 74 | * |
||
| 75 | * @return int |
||
| 76 | */ |
||
| 77 | public function getEdges() |
||
| 78 | { |
||
| 79 | // return the number of edges |
||
| 80 | return $this->edges; |
||
| 81 | } |
||
| 82 | |||
| 83 | /** |
||
| 84 | * Returns the vertices adjacent to $vertex |
||
| 85 | * |
||
| 86 | * @param int $vertex |
||
| 87 | * @return array |
||
| 88 | */ |
||
| 89 | public function adjacent(int $vertex) |
||
| 90 | { |
||
| 91 | // validate the vertex |
||
| 92 | Digraph::validateVertex($vertex, $this->getVertices()); |
||
| 93 | // return the adjacent vertices to it |
||
| 94 | return $this->adjacencyList[$vertex]; |
||
| 95 | } |
||
| 96 | |||
| 97 | /** |
||
| 98 | * @param int $vertex |
||
| 99 | * @return int |
||
| 100 | */ |
||
| 101 | public function degree(int $vertex) |
||
| 102 | { |
||
| 103 | // validate the vertex |
||
| 104 | Digraph::validateVertex($vertex, $this->getVertices()); |
||
| 105 | // return the count of neighbors |
||
| 106 | return count($this->adjacent($vertex)); |
||
| 107 | } |
||
| 108 | |||
| 109 | /** |
||
| 110 | * Utility function |
||
| 111 | * |
||
| 112 | * @param int $vertex |
||
| 113 | * @param int $vertices |
||
| 114 | */ |
||
| 115 | public static function validateVertex(int $vertex, int $vertices) |
||
| 116 | { |
||
| 117 | // run the check |
||
| 118 | if ($vertex < 0 || $vertex >= $vertices) { |
||
| 119 | // this vertex is not valid |
||
| 120 | throw new InvalidArgumentException(sprintf( |
||
| 121 | 'vertex %d is not between 0 and %d', |
||
| 122 | $vertex, |
||
| 123 | $vertices - 1 |
||
| 124 | )); |
||
| 125 | } |
||
| 126 | } |
||
| 127 | |||
| 128 | /** |
||
| 129 | * Returns a string representation of this graph. |
||
| 130 | */ |
||
| 131 | public function __toString() |
||
| 132 | { |
||
| 133 | $vertices = $this->getVertices(); |
||
| 134 | // init |
||
| 135 | $buffer = []; |
||
| 136 | // add |
||
| 137 | $buffer[] = sprintf( |
||
| 138 | '%d vertices, %d edges', |
||
| 139 | $vertices, |
||
| 140 | $this->getEdges() |
||
| 141 | ); |
||
| 142 | // iterate over the vertices |
||
| 143 | for ($vertex = 0; $vertex < $vertices; $vertex++) { |
||
| 144 | // get the adjacent vertices |
||
| 145 | $adjacentVertices = $this->adjacent($vertex); |
||
| 146 | // add |
||
| 147 | $buffer[] = sprintf( |
||
| 148 | '%d : %s', |
||
| 149 | $vertex, |
||
| 150 | implode(' ', $adjacentVertices) |
||
| 151 | ); |
||
| 152 | } |
||
| 153 | // convert to string |
||
| 154 | return implode(PHP_EOL, $buffer); |
||
| 155 | } |
||
| 156 | |||
| 157 | /** |
||
| 158 | * @param string $input |
||
| 159 | * @param int $vertices |
||
| 160 | * @param bool $weight |
||
| 161 | * @return array |
||
| 162 | */ |
||
| 163 | protected static function parseEdge(string $input,int $vertices, bool $weight = false) |
||
| 164 | { |
||
| 165 | // clean |
||
| 166 | $trimmed = trim($input); |
||
| 167 | // parse |
||
| 168 | $exploded = explode(' ', $trimmed); |
||
| 169 | // filter |
||
| 170 | $filtered = array_filter($exploded, function($vertex) { |
||
| 171 | // make sure it valid |
||
| 172 | return (!empty($vertex) || (strlen($vertex) > 0)); |
||
| 173 | }); |
||
| 174 | // get values |
||
| 175 | $edge = array_values($filtered); |
||
| 176 | // get v |
||
| 177 | $v = (int)filter_var( |
||
| 178 | $edge[0], |
||
| 179 | FILTER_SANITIZE_NUMBER_INT |
||
| 180 | ); |
||
| 181 | // validate it |
||
| 182 | Graph::validateVertex($v, $vertices); |
||
| 183 | // get w |
||
| 184 | $w = (int)filter_var( |
||
| 185 | $edge[1], |
||
| 186 | FILTER_SANITIZE_NUMBER_INT |
||
| 187 | ); |
||
| 188 | // validate it |
||
| 189 | Graph::validateVertex($w, $vertices); |
||
| 190 | // create set |
||
| 191 | $set = [ |
||
| 192 | $v, |
||
| 193 | $w, |
||
| 194 | ]; |
||
| 195 | // check if weight needs to be added |
||
| 196 | if ($weight) { |
||
| 197 | // get weight |
||
| 198 | $inputWeight = (int)filter_var( |
||
| 199 | $edge[2], |
||
| 200 | FILTER_SANITIZE_NUMBER_INT |
||
| 201 | ); |
||
| 202 | // add it to the set |
||
| 203 | $set[] = $inputWeight; |
||
| 204 | } |
||
| 205 | // return set |
||
| 206 | return $set; |
||
| 207 | } |
||
| 208 | |||
| 209 | /** |
||
| 210 | * @param resource $handle |
||
| 211 | * @return int[] |
||
| 212 | */ |
||
| 213 | protected static function parseGraphVEFromResource($handle) |
||
| 214 | { |
||
| 215 | // read from stream |
||
| 216 | $first = fgets($handle); |
||
| 217 | // read from stream |
||
| 218 | $second = fgets($handle); |
||
| 219 | // delegate to string parser |
||
| 220 | return self::parseGraphVEFromString( |
||
| 221 | $first, |
||
| 222 | $second |
||
| 223 | ); |
||
| 224 | } |
||
| 225 | |||
| 226 | /** |
||
| 227 | * @param string $first |
||
| 228 | * @param string $second |
||
| 229 | * @return int[] |
||
| 230 | */ |
||
| 231 | protected static function parseGraphVEFromString(string $first, string $second) |
||
| 232 | { |
||
| 233 | // open the stream for reading |
||
| 234 | $vertices = (int)filter_var( |
||
| 235 | $first, |
||
| 236 | FILTER_SANITIZE_NUMBER_INT |
||
| 237 | ); |
||
| 238 | // sanity check |
||
| 239 | if ($vertices < 0) { |
||
| 240 | // bad state |
||
| 241 | throw new InvalidArgumentException( |
||
| 242 | 'number of vertices in a Graph must be non-negative' |
||
| 243 | ); |
||
| 244 | } |
||
| 245 | |||
| 246 | // read in the amount of edges in the stream |
||
| 247 | $edges = (int)filter_var( |
||
| 248 | $second, |
||
| 249 | FILTER_SANITIZE_NUMBER_INT |
||
| 250 | ); |
||
| 251 | // sanity check |
||
| 252 | if ($edges < 0) { |
||
| 253 | // bad state |
||
| 254 | throw new InvalidArgumentException( |
||
| 255 | 'number of edges in a Graph must be non-negative' |
||
| 256 | ); |
||
| 257 | } |
||
| 258 | // return the set |
||
| 259 | return [ |
||
| 260 | $vertices, |
||
| 261 | $edges |
||
| 262 | ]; |
||
| 263 | } |
||
| 264 | |||
| 265 | /** |
||
| 266 | * @param Graph $graph |
||
| 267 | * @param int $vertices |
||
| 268 | * @param int $edges |
||
| 269 | * @param resource $handle |
||
| 270 | */ |
||
| 271 | protected static function buildWeightedEdgesFromHandle( |
||
| 272 | Graph $graph, |
||
| 273 | int $vertices, |
||
| 274 | int $edges, |
||
| 275 | $handle |
||
| 276 | ) { |
||
| 277 | // read in the edges |
||
| 278 | for ($i = 0; $i < $edges; $i++) { |
||
| 279 | // fet from source |
||
| 280 | $raw = fgets($handle); |
||
| 281 | // parse data |
||
| 282 | list ( |
||
| 283 | $v, |
||
| 284 | $w, |
||
| 285 | $weight |
||
| 286 | ) = self::parseEdge($raw, $vertices, true); |
||
| 287 | // re-use var here |
||
| 288 | $edge = new Edge($v, $w, $weight); |
||
| 289 | // add to the graph |
||
| 290 | $graph->addEdge($edge); |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 291 | } |
||
| 292 | } |
||
| 293 | |||
| 294 | /** |
||
| 295 | * @param Graph $graph |
||
| 296 | * @param int $vertices |
||
| 297 | * @param int $edges |
||
| 298 | * @param array $lines |
||
| 299 | */ |
||
| 300 | protected static function buildWeightedEdgesFromString( |
||
| 301 | Graph $graph, |
||
| 302 | int $vertices, |
||
| 303 | int $edges, |
||
| 304 | array $lines |
||
| 305 | ) { |
||
| 306 | // read in the edges |
||
| 307 | for ($i = 0; $i < $edges; $i++) { |
||
| 308 | // fet from source |
||
| 309 | $raw = $lines[$i]; |
||
| 310 | // parse data |
||
| 311 | list ( |
||
| 312 | $v, |
||
| 313 | $w, |
||
| 314 | $weight |
||
| 315 | ) = self::parseEdge($raw, $vertices, true); |
||
| 316 | // re-use var |
||
| 317 | $edge = new Edge($v, $w, $weight); |
||
| 318 | // add to the graph |
||
| 319 | $graph->addEdge($edge); |
||
| 320 | } |
||
| 321 | } |
||
| 322 | } |