[$from_idx1, $from_idx2, ...] format */ protected $dag; function __construct($data = null) { $data = $data ? array_values($data) : array(); $this->data = $data; $this->dag = array_fill_keys(array_keys($data), array()); } /** * Add another node/vertex * @param $item anything - The item to add to the graph */ function additem($item) { $this->data[] = $item; $this->dag[] = array(); } /** * Add an edge from one vertex to another * @param $from integer|any - The index in $data of the node/vertex, or the node/vertex itself, that the edge goes from * @param $to integer|any - The index in $data of the node/vertex, or the node/vertex itself, that the edge goes to * * When passing actual nodes (as opposed to indexes), uses array_search with strict = true to find */ function addedge($from, $to) { $i = is_numeric($from) ? $from : array_search($from, $this->data, true); $j = is_numeric($to) ? $to : array_search($to, $this->data, true); if ($i === false) throw new Exception("Couldnt find 'from' item in data when adding edge to DAG"); if ($j === false) throw new Exception("Couldnt find 'to' item in data when adding edge to DAG"); if (!isset($this->dag[$j])) $this->dag[$j] = array(); $this->dag[$j][] = $i; } /** * Sort graph so that each node (a) comes before any nodes (b) where an edge exists from a to b * @return array - The nodes * @throws Exception - If the graph is cyclic (and so can't be sorted) */ function sort() { $data = $this->data; $dag = $this->dag; $sorted = array(); while (true) { $withedges = array_filter($dag, 'count'); $starts = array_diff_key($dag, $withedges); if (!count($starts)) break; foreach ($starts as $i => $foo) $sorted[] = $data[$i]; foreach ($withedges as $j => $deps) { $withedges[$j] = array_diff($withedges[$j], array_keys($starts)); } $dag = $withedges; } if ($dag) throw new Exception("DAG has cyclic requirements"); return $sorted; } }