Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,35 @@
<?php
namespace Fhaculty\Graph\Attribute;
/**
* Implemented by any entity that is aware of additional attributes
*
* Each attribute consists of a name (string) and an arbitrary value.
*/
interface AttributeAware
{
/**
* get a single attribute with the given $name (or return $default if attribute was not found)
*
* @param string $name
* @param mixed $default to return if attribute was not found
* @return mixed
*/
public function getAttribute($name, $default = null);
/**
* set a single attribute with the given $name to given $value
*
* @param string $name
* @param mixed $value
*/
public function setAttribute($name, $value);
/**
* get a container for all attributes
*
* @return AttributeBag
*/
public function getAttributeBag();
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Fhaculty\Graph\Attribute;
/**
* Interface to container that represents multiple attributes
*/
interface AttributeBag extends AttributeAware
{
// public function getAttribute($name, $default);
// public function setAttribute($name, $value);
// public function getAttributeBag();
/**
* set an array of additional attributes
*
* @param array $attributes
*/
public function setAttributes(array $attributes);
/**
* get an array of all attributes
*
* @return array
*/
public function getAttributes();
}

View File

@@ -0,0 +1,76 @@
<?php
namespace Fhaculty\Graph\Attribute;
/**
* A fairly standard AttributeBag container.
*
* This container passes and returns attributes by value. It is mutable,
* however, so multiple references to the container will update in kind.
*/
class AttributeBagContainer implements AttributeBag
{
/**
* @var array
*/
private $attributes = array();
/**
* get a single attribute with the given $name (or return $default if attribute was not found)
*
* @param string $name
* @param mixed $default to return if attribute was not found
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
}
/**
* set a single attribute with the given $name to given $value
*
* @param string $name
* @param mixed $value
* @return self For a fluid interface.
*/
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
return $this;
}
/**
* get an array of all attributes
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* set an array of additional attributes
*
* @param array $attributes
* @return self For a fluid interface.
*/
public function setAttributes(array $attributes)
{
$this->attributes = $attributes + $this->attributes;
return $this;
}
/**
* get a container for all attributes
*
* @return AttributeBag
*/
public function getAttributeBag()
{
return $this;
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace Fhaculty\Graph\Attribute;
/**
* An attribute bag that automatically prefixes a given namespace.
*
* For example, you can use this class to prefix the attributes using a vendor
* name, like "myvendor.item.". If another vendor shares the base attribute
* bag, it can use a different prefix, like "otherProduct.item.". This allows
* both libraries to have attributes with the same name without having them
* conflict. For example, the attribute "id" would be stored separately as
* "myvendor.item.id" and "otherProduct.item.id".
*/
class AttributeBagNamespaced implements AttributeBag
{
/**
* @var AttributeBag
*/
private $bag;
/**
* @var string
*/
private $prefix;
/**
* Initialize the attribute bag with a prefix to use as a namespace for the attributes.
*
* @param AttributeAware $bag The bag to store the prefixed attributes in.
* @param string $prefix The prefix to prepend to all attributes before
* storage. This prefix acts as a namespace to separate attributes.
*/
public function __construct(AttributeAware $bag, $prefix)
{
if (!($bag instanceof AttributeBag)) {
$bag = $bag->getAttributeBag();
}
$this->bag = $bag;
$this->prefix = $prefix;
}
/**
* get a single attribute with the given $name (or return $default if attribute was not found)
*
* This prefixes the attribute name before requesting from the base bag.
*
* @param string $name
* @param mixed $default to return if attribute was not found
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return $this->bag->getAttribute($this->prefix . $name, $default);
}
/**
* set a single attribute with the given $name to given $value
*
* This prefixes the attribute name before setting in the base bag.
*
* @param string $name
* @param mixed $value
* @return void
*/
public function setAttribute($name, $value)
{
$this->bag->setAttribute($this->prefix . $name, $value);
}
/**
* get an array of all attributes
*
* The prefix will not be included in the returned attribute keys.
*
* @return array
*/
public function getAttributes()
{
$attributes = array();
$len = strlen($this->prefix);
foreach ($this->bag->getAttributes() as $name => $value) {
if (strpos($name, $this->prefix) === 0) {
$attributes[substr($name, $len)] = $value;
}
}
return $attributes;
}
/**
* set an array of additional attributes
*
* Each attribute is prefixed before setting in the base bag.
*
* @param array $attributes
* @return void
*/
public function setAttributes(array $attributes)
{
foreach ($attributes as $name => $value) {
$this->bag->setAttribute($this->prefix . $name, $value);
}
}
/**
* get a container for all attributes
*
* @return AttributeBag
*/
public function getAttributeBag()
{
return $this;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Fhaculty\Graph\Attribute;
/**
* The basic attribute bag, but using a reference to the base attribute array.
*
* This container passes and returns attributes by value, but stores them in a
* pass-by-reference array. It is mutable, however, so multiple references to
* the container will update in kind.
*/
class AttributeBagReference implements AttributeBag
{
/**
* @var array
*/
private $attributes;
/**
* Initialize the attribute bag with the base attribute array.
*
* The given array is pass-by-reference, so updates to the array here or in
* calling code will be reflected everywhere.
*
* @param array $attributes The pass-by-reference attributes.
*/
public function __construct(array &$attributes)
{
$this->attributes =& $attributes;
}
/**
* get a single attribute with the given $name (or return $default if attribute was not found)
*
* @param string $name
* @param mixed $default to return if attribute was not found
* @return mixed
*/
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
}
/**
* set a single attribute with the given $name to given $value
*
* @param string $name
* @param mixed $value
* @return self For a fluid interface.
*/
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
return $this;
}
/**
* get an array of all attributes
*
* @return array
*/
public function getAttributes()
{
return $this->attributes;
}
/**
* set an array of additional attributes
*
* @param array $attributes
* @return self For a fluid interface.
*/
public function setAttributes(array $attributes)
{
$this->attributes = $attributes + $this->attributes;
return $this;
}
/**
* get a container for all attributes
*
* @return AttributeBag
*/
public function getAttributeBag()
{
return $this;
}
}

308
vendor/clue/graph/src/Edge/Base.php vendored Normal file
View File

@@ -0,0 +1,308 @@
<?php
namespace Fhaculty\Graph\Edge;
use Fhaculty\Graph\Attribute\AttributeAware;
use Fhaculty\Graph\Attribute\AttributeBagReference;
use Fhaculty\Graph\Exception\BadMethodCallException;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Exception\LogicException;
use Fhaculty\Graph\Exception\RangeException;
use Fhaculty\Graph\Graph;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Set\VerticesAggregate;
use Fhaculty\Graph\Vertex;
abstract class Base implements VerticesAggregate, AttributeAware
{
/**
* weight of this edge
*
* @var float|int|NULL
* @see self::getWeight()
*/
protected $weight = NULL;
/**
* maximum capacity (maximum flow)
*
* @var float|int|NULL
* @see self::getCapacity()
*/
protected $capacity = NULL;
/**
* flow (capacity currently in use)
*
* @var float|int|NULL
* @see self::getFlow()
*/
protected $flow = NULL;
protected $attributes = array();
/**
* get Vertices that are a target of this edge
*
* @return Vertices
*/
abstract public function getVerticesTarget();
/**
* get Vertices that are the start of this edge
*
* @return Vertices
*/
abstract public function getVerticesStart();
/**
* return true if this edge is an outgoing edge of the given vertex (i.e. the given vertex is a valid start vertex of this edge)
*
* @param Vertex $startVertex
* @return bool
* @uses Vertex::getVertexToFrom()
*/
abstract public function hasVertexStart(Vertex $startVertex);
/**
* return true if this edge is an ingoing edge of the given vertex (i . e. the given vertex is a valid end vertex of this edge)
*
* @param Vertex $targetVertex
* @return bool
* @uses Vertex::getVertexFromTo()
*/
abstract function hasVertexTarget(Vertex $targetVertex);
abstract public function isConnection(Vertex $from, Vertex $to);
/**
* returns whether this edge is actually a loop
*
* @return bool
*/
abstract public function isLoop();
/**
* get target vertex we can reach with this edge from the given start vertex
*
* @param Vertex $startVertex
* @return Vertex
* @throws InvalidArgumentException if given $startVertex is not a valid start
* @see self::hasEdgeFrom() to check if given start is valid
*/
abstract public function getVertexToFrom(Vertex $startVertex);
/**
* get start vertex which can reach us(the given end vertex) with this edge
*
* @param Vertex $endVertex
* @return Vertex
* @throws InvalidArgumentException if given $startVertex is not a valid end
* @see self::hasEdgeFrom() to check if given start is valid
*/
abstract public function getVertexFromTo(Vertex $endVertex);
/**
* return weight of edge
*
* @return float|int|NULL numeric weight of edge or NULL=not set
*/
public function getWeight()
{
return $this->weight;
}
/**
* set new weight for edge
*
* @param float|int|NULL $weight new numeric weight of edge or NULL=unset weight
* @return self $this (chainable)
* @throws InvalidArgumentException if given weight is not numeric
*/
public function setWeight($weight)
{
if ($weight !== NULL && !is_float($weight) && !is_int($weight)) {
throw new InvalidArgumentException('Invalid weight given - must be numeric or NULL');
}
$this->weight = $weight;
return $this;
}
/**
* get total capacity of this edge
*
* @return float|int|NULL numeric capacity or NULL=not set
*/
public function getCapacity()
{
return $this->capacity;
}
/**
* get the capacity remaining (total capacity - current flow)
*
* @return float|int|NULL numeric capacity remaining or NULL=no upper capacity set
*/
public function getCapacityRemaining()
{
if ($this->capacity === NULL) {
return NULL;
}
return $this->capacity - $this->flow;
}
/**
* set new total capacity of this edge
*
* @param float|int|NULL $capacity
* @return self $this (chainable)
* @throws InvalidArgumentException if $capacity is invalid (not numeric or negative)
* @throws RangeException if current flow exceeds new capacity
*/
public function setCapacity($capacity)
{
if ($capacity !== NULL) {
if (!is_float($capacity) && !is_int($capacity)) {
throw new InvalidArgumentException('Invalid capacity given - must be numeric');
}
if ($capacity < 0) {
throw new InvalidArgumentException('Capacity must not be negative');
}
if ($this->flow !== NULL && $this->flow > $capacity) {
throw new RangeException('Current flow of ' . $this->flow . ' exceeds new capacity');
}
}
$this->capacity = $capacity;
return $this;
}
/**
* get current flow (capacity currently in use)
*
* @return float|int|NULL numeric flow or NULL=not set
*/
public function getFlow()
{
return $this->flow;
}
/**
* set new total flow (capacity currently in use)
*
* @param float|int|NULL $flow
* @return self $this (chainable)
* @throws InvalidArgumentException if $flow is invalid (not numeric or negative)
* @throws RangeException if flow exceeds current maximum capacity
*/
public function setFlow($flow)
{
if ($flow !== NULL) {
if (!is_float($flow) && !is_int($flow)) {
throw new InvalidArgumentException('Invalid flow given - must be numeric');
}
if ($flow < 0) {
throw new InvalidArgumentException('Flow must not be negative');
}
if ($this->capacity !== NULL && $flow > $this->capacity) {
throw new RangeException('New flow exceeds maximum capacity');
}
}
$this->flow = $flow;
return $this;
}
/**
* get set of all Vertices this edge connects
*
* @return Vertices
*/
//abstract public function getVertices();
/**
* get graph instance this edge is attached to
*
* @return Graph
* @throws LogicException
*/
public function getGraph()
{
foreach ($this->getVertices() as $vertex) {
return $vertex->getGraph();
// the following code can only be reached if this edge does not
// contain any vertices (invalid state), so ignore its coverage
// @codeCoverageIgnoreStart
}
throw new LogicException('Internal error: should not be reached');
// @codeCoverageIgnoreEnd
}
/**
* destroy edge and remove reference from vertices and graph
*
* @uses Graph::removeEdge()
* @uses Vertex::removeEdge()
* @return void
*/
public function destroy()
{
$this->getGraph()->removeEdge($this);
foreach ($this->getVertices() as $vertex) {
$vertex->removeEdge($this);
}
}
/**
* create new clone of this edge between adjacent vertices
*
* @return self new edge
* @uses Graph::createEdgeClone()
*/
public function createEdgeClone()
{
return $this->getGraph()->createEdgeClone($this);
}
/**
* create new clone of this edge inverted (in opposite direction) between adjacent vertices
*
* @return self new edge
* @uses Graph::createEdgeCloneInverted()
*/
public function createEdgeCloneInverted()
{
return $this->getGraph()->createEdgeCloneInverted($this);
}
/**
* do NOT allow cloning of objects
*
* @throws BadMethodCallException
*/
private function __clone()
{
// @codeCoverageIgnoreStart
throw new BadMethodCallException();
// @codeCoverageIgnoreEnd
}
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
}
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
}
public function getAttributeBag()
{
return new AttributeBagReference($this->attributes);
}
}

119
vendor/clue/graph/src/Edge/Directed.php vendored Normal file
View File

@@ -0,0 +1,119 @@
<?php
namespace Fhaculty\Graph\Edge;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Vertex;
class Directed extends Base
{
/**
* source/start vertex
*
* @var Vertex
*/
private $from;
/**
* target/end vertex
*
* @var Vertex
*/
private $to;
/**
* create a new directed Edge from Vertex $from to Vertex $to
*
* @param Vertex $from start/source Vertex
* @param Vertex $to end/target Vertex
* @see Vertex::createEdgeTo() to create directed edges
* @see Vertex::createEdge() to create undirected edges
*/
public function __construct(Vertex $from, Vertex $to)
{
if ($from->getGraph() !== $to->getGraph()) {
throw new InvalidArgumentException('Vertices have to be within the same graph');
}
$this->from = $from;
$this->to = $to;
$from->getGraph()->addEdge($this);
$from->addEdge($this);
$to->addEdge($this);
}
public function getVerticesTarget()
{
return new Vertices(array($this->to));
}
public function getVerticesStart()
{
return new Vertices(array($this->from));
}
public function getVertices()
{
return new Vertices(array($this->from, $this->to));
}
/**
* get end/target vertex
*
* @return Vertex
*/
public function getVertexEnd()
{
return $this->to;
}
/**
* get start vertex
*
* @return Vertex
*/
public function getVertexStart()
{
return $this->from;
}
public function isConnection(Vertex $from, Vertex $to)
{
return ($this->to === $to && $this->from === $from);
}
public function isLoop()
{
return ($this->to === $this->from);
}
public function getVertexToFrom(Vertex $startVertex)
{
if ($this->from !== $startVertex) {
throw new InvalidArgumentException('Invalid start vertex');
}
return $this->to;
}
public function getVertexFromTo(Vertex $endVertex)
{
if ($this->to !== $endVertex) {
throw new InvalidArgumentException('Invalid end vertex');
}
return $this->from;
}
public function hasVertexStart(Vertex $startVertex)
{
return ($this->from === $startVertex);
}
public function hasVertexTarget(Vertex $targetVertex)
{
return ($this->to === $targetVertex);
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace Fhaculty\Graph\Edge;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Vertex;
use Fhaculty\Graph\Set\Vertices;
class Undirected extends Base
{
/**
* vertex a
*
* @var Vertex
*/
private $a;
/**
* vertex b
*
* @var Vertex
*/
private $b;
/**
* create a new undirected edge between given vertices
*
* @param Vertex $a
* @param Vertex $b
* @see Vertex::createEdge() instead
*/
public function __construct(Vertex $a, Vertex $b)
{
if ($a->getGraph() !== $b->getGraph()) {
throw new InvalidArgumentException('Vertices have to be within the same graph');
}
$this->a = $a;
$this->b = $b;
$a->getGraph()->addEdge($this);
$a->addEdge($this);
$b->addEdge($this);
}
public function getVerticesTarget()
{
return new Vertices(array($this->b, $this->a));
}
public function getVerticesStart()
{
return new Vertices(array($this->a, $this->b));
}
public function getVertices()
{
return new Vertices(array($this->a, $this->b));
}
public function isConnection(Vertex $from, Vertex $to)
{
// one way or other way
return (($this->a === $from && $this->b === $to) || ($this->b === $from && $this->a === $to));
}
public function isLoop()
{
return ($this->a === $this->b);
}
public function getVertexToFrom(Vertex $startVertex)
{
if ($this->a === $startVertex) {
return $this->b;
} elseif ($this->b === $startVertex) {
return $this->a;
} else {
throw new InvalidArgumentException('Invalid start vertex');
}
}
public function getVertexFromTo(Vertex $endVertex)
{
if ($this->a === $endVertex) {
return $this->b;
} elseif ($this->b === $endVertex) {
return $this->a;
} else {
throw new InvalidArgumentException('Invalid end vertex');
}
}
public function hasVertexStart(Vertex $startVertex)
{
return ($this->a === $startVertex || $this->b === $startVertex);
}
public function hasVertexTarget(Vertex $targetVertex)
{
// same implementation as direction does not matter
return $this->hasVertexStart($targetVertex);
}
}

7
vendor/clue/graph/src/Exception.php vendored Normal file
View File

@@ -0,0 +1,7 @@
<?php
namespace Fhaculty\Graph;
interface Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class BadMethodCallException extends \BadMethodCallException implements Graph\Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class DomainException extends \DomainException implements Graph\Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class InvalidArgumentException extends \InvalidArgumentException implements Graph\Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class LogicException extends \LogicException implements Graph\Exception
{
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph\Walk;
use Fhaculty\Graph;
class NegativeCycleException extends UnexpectedValueException implements Graph\Exception
{
/**
* instance of the cycle
*
* @var Walk
*/
private $cycle;
public function __construct($message, $code = NULL, $previous = NULL, Walk $cycle = null)
{
// $cycle is required, but required argument may not appear after option arguments as of PHP 8
if ($cycle === null) {
throw new \InvalidArgumentException('Missing required cycle');
}
parent::__construct($message, $code, $previous);
$this->cycle = $cycle;
}
/**
*
* @return Walk
*/
public function getCycle()
{
return $this->cycle;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class OutOfBoundsException extends \OutOfBoundsException implements Graph\Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class OverflowException extends \OverflowException implements Graph\Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class RangeException extends \RangeException implements Graph\Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class RuntimeException extends \RuntimeException implements Graph\Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class UnderflowException extends \UnderflowException implements Graph\Exception
{
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Fhaculty\Graph\Exception;
use Fhaculty\Graph;
class UnexpectedValueException extends \UnexpectedValueException implements Graph\Exception
{
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Fhaculty\Graph\Exporter;
use Fhaculty\Graph\Graph;
interface ExporterInterface
{
public function getOutput(Graph $graph);
}

464
vendor/clue/graph/src/Graph.php vendored Normal file
View File

@@ -0,0 +1,464 @@
<?php
namespace Fhaculty\Graph;
use Fhaculty\Graph\Attribute\AttributeAware;
use Fhaculty\Graph\Attribute\AttributeBagReference;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
use Fhaculty\Graph\Exception\BadMethodCallException;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Exception\OutOfBoundsException;
use Fhaculty\Graph\Exception\OverflowException;
use Fhaculty\Graph\Exception\RuntimeException;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Set\DualAggregate;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Set\VerticesMap;
class Graph implements DualAggregate, AttributeAware
{
protected $verticesStorage = array();
protected $vertices;
protected $edgesStorage = array();
protected $edges;
protected $attributes = array();
public function __construct()
{
$this->vertices = VerticesMap::factoryArrayReference($this->verticesStorage);
$this->edges = Edges::factoryArrayReference($this->edgesStorage);
}
/**
* return set of Vertices added to this graph
*
* @return Vertices
*/
public function getVertices()
{
return $this->vertices;
}
/**
* return set of ALL Edges added to this graph
*
* @return Edges
*/
public function getEdges()
{
return $this->edges;
}
/**
* create a new Vertex in the Graph
*
* @param int|NULL $id new vertex ID to use (defaults to NULL: use next free numeric ID)
* @param bool $returnDuplicate normal operation is to throw an exception if given id already exists. pass true to return original vertex instead
* @return Vertex (chainable)
* @throws InvalidArgumentException if given vertex $id is invalid
* @throws OverflowException if given vertex $id already exists and $returnDuplicate is not set
* @uses Vertex::getId()
*/
public function createVertex($id = NULL, $returnDuplicate = false)
{
// no ID given
if ($id === NULL) {
$id = $this->getNextId();
}
if ($returnDuplicate && $this->vertices->hasVertexId($id)) {
return $this->vertices->getVertexId($id);
}
return new Vertex($this, $id);
}
/**
* create a new Vertex in this Graph from the given input Vertex of another graph
*
* @param Vertex $originalVertex
* @return Vertex new vertex in this graph
* @throws RuntimeException if vertex with this ID already exists
*/
public function createVertexClone(Vertex $originalVertex)
{
$id = $originalVertex->getId();
if ($this->vertices->hasVertexId($id)) {
throw new RuntimeException('Id of cloned vertex already exists');
}
$newVertex = new Vertex($this, $id);
// TODO: properly set attributes of vertex
$newVertex->getAttributeBag()->setAttributes($originalVertex->getAttributeBag()->getAttributes());
$newVertex->setBalance($originalVertex->getBalance());
$newVertex->setGroup($originalVertex->getGroup());
return $newVertex;
}
/**
* create new clone/copy of this graph - copy all attributes and vertices, but do NOT copy edges
*
* using this method is faster than creating a new graph and calling createEdgeClone() yourself
*
* @return Graph
*/
public function createGraphCloneEdgeless()
{
$graph = new Graph();
$graph->getAttributeBag()->setAttributes($this->getAttributeBag()->getAttributes());
// TODO: set additional graph attributes
foreach ($this->getVertices() as $originalVertex) {
$vertex = $graph->createVertexClone($originalVertex);
// $graph->vertices[$vid] = $vertex;
}
return $graph;
}
/**
* create new clone/copy of this graph - copy all attributes and vertices. but only copy all given edges
*
* @param Edges|Edge[] $edges set or array of edges to be cloned
* @return Graph
* @uses Graph::createGraphCloneEdgeless()
* @uses Graph::createEdgeClone() for each edge to be cloned
*/
public function createGraphCloneEdges($edges)
{
$graph = $this->createGraphCloneEdgeless();
foreach ($edges as $edge) {
$graph->createEdgeClone($edge);
}
return $graph;
}
/**
* create new clone/copy of this graph - copy all attributes, vertices and edges
*
* @return Graph
* @uses Graph::createGraphCloneEdges() to clone graph with current edges
*/
public function createGraphClone()
{
return $this->createGraphCloneEdges($this->edges);
}
/**
* create a new clone/copy of this graph - copy all attributes and given vertices and its edges
*
* @param Vertices $vertices set of vertices to keep
* @return Graph
* @uses Graph::createGraphClone() to create a complete clone
* @uses Vertex::destroy() to remove unneeded vertices again
*/
public function createGraphCloneVertices($vertices)
{
$verticesKeep = Vertices::factory($vertices);
$graph = $this->createGraphClone();
foreach ($graph->getVertices()->getMap() as $vid => $vertex) {
if (!$verticesKeep->hasVertexId($vid)) {
$vertex->destroy();
}
}
return $graph;
}
/**
* create new clone of the given edge between adjacent vertices
*
* @param Edge $originalEdge original edge (not neccessarily from this graph)
* @return Edge new edge in this graph
* @uses Graph::createEdgeCloneInternal()
*/
public function createEdgeClone(Edge $originalEdge)
{
return $this->createEdgeCloneInternal($originalEdge, 0, 1);
}
/**
* create new clone of the given edge inverted (in opposite direction) between adjacent vertices
*
* @param Edge $originalEdge original edge (not neccessarily from this graph)
* @return Edge new edge in this graph
* @uses Graph::createEdgeCloneInternal()
*/
public function createEdgeCloneInverted(Edge $originalEdge)
{
return $this->createEdgeCloneInternal($originalEdge, 1, 0);
}
/**
* create new clone of the given edge between adjacent vertices
*
* @param Edge $originalEdge original edge from old graph
* @param int $ia index of start vertex
* @param int $ib index of end vertex
* @return Edge new edge in this graph
* @uses Edge::getVertices()
* @uses Graph::getVertex()
* @uses Vertex::createEdge() to create a new undirected edge if given edge was undrected
* @uses Vertex::createEdgeTo() to create a new directed edge if given edge was directed
* @uses Edge::getWeight()
* @uses Edge::setWeight()
* @uses Edge::getFlow()
* @uses Edge::setFlow()
* @uses Edge::getCapacity()
* @uses Edge::setCapacity()
*/
private function createEdgeCloneInternal(Edge $originalEdge, $ia, $ib)
{
$ends = $originalEdge->getVertices()->getIds();
// get start vertex from old start vertex id
$a = $this->getVertex($ends[$ia]);
// get target vertex from old target vertex id
$b = $this->getVertex($ends[$ib]);
if ($originalEdge instanceof EdgeDirected) {
$newEdge = $a->createEdgeTo($b);
} else {
// create new edge between new a and b
$newEdge = $a->createEdge($b);
}
// TODO: copy edge attributes
$newEdge->getAttributeBag()->setAttributes($originalEdge->getAttributeBag()->getAttributes());
$newEdge->setWeight($originalEdge->getWeight());
$newEdge->setFlow($originalEdge->getFlow());
$newEdge->setCapacity($originalEdge->getCapacity());
return $newEdge;
}
/**
* create the given number of vertices or given array of Vertex IDs
*
* @param int|array $n number of vertices to create or array of Vertex IDs to create
* @return Vertices set of Vertices created
* @uses Graph::getNextId()
*/
public function createVertices($n)
{
$vertices = array();
if (is_int($n) && $n >= 0) {
for ($id = $this->getNextId(), $n += $id; $id < $n; ++$id) {
$vertices[$id] = new Vertex($this, $id);
}
} elseif (is_array($n)) {
// array given => check to make sure all given IDs are available (atomic operation)
foreach ($n as $id) {
if (!is_int($id) && !is_string($id)) {
throw new InvalidArgumentException('All Vertex IDs have to be of type integer or string');
} elseif ($this->vertices->hasVertexId($id)) {
throw new OverflowException('Given array of Vertex IDs contains an ID that already exists. Given IDs must be unique');
} elseif (isset($vertices[$id])) {
throw new InvalidArgumentException('Given array of Vertex IDs contain duplicate IDs. Given IDs must be unique');
}
// temporary marker to check for duplicate IDs in the array
$vertices[$id] = false;
}
// actually create all requested vertices
foreach ($n as $id) {
$vertices[$id] = new Vertex($this, $id);
}
} else {
throw new InvalidArgumentException('Invalid number of vertices given. Must be non-negative integer or an array of Vertex IDs');
}
return new Vertices($vertices);
}
/**
* get next free/unused/available vertex ID
*
* its guaranteed there's NO other vertex with a greater ID
*
* @return int
*/
private function getNextId()
{
if (!$this->verticesStorage) {
return 0;
}
// auto ID
return max(array_map('intval', array_keys($this->verticesStorage)))+1;
}
/**
* returns the Vertex with identifier $id
*
* @param int|string $id identifier of Vertex
* @return Vertex
* @throws OutOfBoundsException if given vertex ID does not exist
*/
public function getVertex($id)
{
return $this->vertices->getVertexId($id);
}
/**
* checks whether given vertex ID exists in this graph
*
* @param int|string $id identifier of Vertex
* @return bool
*/
public function hasVertex($id)
{
return $this->vertices->hasVertexId($id);
}
/**
* adds a new Vertex to the Graph (MUST NOT be called manually!)
*
* @param Vertex $vertex instance of the new Vertex
* @return void
* @internal
* @see self::createVertex() instead!
*/
public function addVertex(Vertex $vertex)
{
if (isset($this->verticesStorage[$vertex->getId()])) {
throw new OverflowException('ID must be unique');
}
$this->verticesStorage[$vertex->getId()] = $vertex;
}
/**
* adds a new Edge to the Graph (MUST NOT be called manually!)
*
* @param Edge $edge instance of the new Edge
* @return void
* @internal
* @see Vertex::createEdge() instead!
*/
public function addEdge(Edge $edge)
{
$this->edgesStorage []= $edge;
}
/**
* remove the given edge from list of connected edges (MUST NOT be called manually!)
*
* @param Edge $edge
* @return void
* @throws InvalidArgumentException if given edge does not exist (should not ever happen)
* @internal
* @see Edge::destroy() instead!
*/
public function removeEdge(Edge $edge)
{
try {
unset($this->edgesStorage[$this->edges->getIndexEdge($edge)]);
}
catch (OutOfBoundsException $e) {
throw new InvalidArgumentException('Invalid Edge does not exist in this Graph');
}
}
/**
* remove the given vertex from list of known vertices (MUST NOT be called manually!)
*
* @param Vertex $vertex
* @return void
* @throws InvalidArgumentException if given vertex does not exist (should not ever happen)
* @internal
* @see Vertex::destroy() instead!
*/
public function removeVertex(Vertex $vertex)
{
try {
unset($this->verticesStorage[$this->vertices->getIndexVertex($vertex)]);
}
catch (OutOfBoundsException $e) {
throw new InvalidArgumentException('Invalid Vertex does not exist in this Graph');
}
}
/**
* Extracts edge from this graph
*
* @param Edge $edge
* @return Edge
* @throws UnderflowException if no edge was found
* @throws OverflowException if multiple edges match
*/
public function getEdgeClone(Edge $edge)
{
// Extract endpoints from edge
$vertices = $edge->getVertices()->getVector();
return $this->getEdgeCloneInternal($edge, $vertices[0], $vertices[1]);
}
/**
* Extracts inverted edge from this graph
*
* @param Edge $edge
* @return Edge
* @throws UnderflowException if no edge was found
* @throws OverflowException if multiple edges match
*/
public function getEdgeCloneInverted(Edge $edge)
{
// Extract endpoints from edge
$vertices = $edge->getVertices()->getVector();
return $this->getEdgeCloneInternal($edge, $vertices[1], $vertices[0]);
}
private function getEdgeCloneInternal(Edge $edge, Vertex $startVertex, Vertex $targetVertex)
{
// Get original vertices from resultgraph
$residualGraphEdgeStartVertex = $this->getVertex($startVertex->getId());
$residualGraphEdgeTargetVertex = $this->getVertex($targetVertex->getId());
// Now get the edge
$residualEdgeArray = $residualGraphEdgeStartVertex->getEdgesTo($residualGraphEdgeTargetVertex);
$residualEdgeArray = Edges::factory($residualEdgeArray)->getVector();
// Check for parallel edges
if (!$residualEdgeArray) {
throw new UnderflowException('No original edges for given cloned edge found');
} elseif (count($residualEdgeArray) !== 1) {
throw new OverflowException('More than one cloned edge? Parallel edges (multigraph) not supported');
}
return $residualEdgeArray[0];
}
/**
* do NOT allow cloning of objects (MUST NOT be called!)
*
* @throws BadMethodCallException
* @see Graph::createGraphClone() instead
*/
private function __clone()
{
// @codeCoverageIgnoreStart
throw new BadMethodCallException();
// @codeCoverageIgnoreEnd
}
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
}
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
}
public function getAttributeBag()
{
return new AttributeBagReference($this->attributes);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Fhaculty\Graph\Set;
/**
* A DualAggregate provides access to both its Vertices and its Edges
*
* This is the simple base interface for any Graph-like structure / data type
* which contains a Set of Edges and a Set of Vertices, such as the Graph class
* itself and the Walk class.
*/
interface DualAggregate extends VerticesAggregate, EdgesAggregate
{
/**
* returns a set of ALL Edges in this graph
*
* @return Edges
*/
// abstract public function getEdges();
/**
* returns a set of all Vertices
*
* @return Vertices
*/
// abstract public function getVertices();
}

487
vendor/clue/graph/src/Set/Edges.php vendored Normal file
View File

@@ -0,0 +1,487 @@
<?php
namespace Fhaculty\Graph\Set;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Exception\OutOfBoundsException;
use Fhaculty\Graph\Exception\UnderflowException;
/**
* A Set of Edges
*
* Contains any number of Edge (directed and/or undirected) instances.
*
* The Set is a readonly instance and it provides methods to get single Edge
* instances or to get a new Set of Edges. This way it's safe to pass around
* the original Set of Edges, because it will never be modified.
*/
class Edges implements \Countable, \IteratorAggregate, EdgesAggregate
{
/**
* order by edge weight
*
* @var int
* @see Edge::getWeight()
*/
const ORDER_WEIGHT = 1;
/**
* order by edge capacity
*
* @var int
* @see Edge::getCapacity()
*/
const ORDER_CAPACITY = 2;
/**
* order by remaining capacity on edge (maximum capacity - current flow)
*
* @var int
* @see Edge::getCapacityRemaining()
*/
const ORDER_CAPACITY_REMAINING = 3;
/**
* order by edge flow
*
* @var int
* @see Edge::getFlow()
*/
const ORDER_FLOW = 4;
/**
* random/shuffled order
*
* @var int
*/
const ORDER_RANDOM = 5;
protected $edges = array();
/**
* create new Edges instance
*
* You can pass in just about anything that can be expressed as a Set of
* Edges, such as:
* - an array of Edge instances
* - any Algorithm that implements the EdgesAggregate interface
* - a Graph instance or
* - an existing Set of Edges which will be returned as-is
*
* @param array|Edges|EdgesAggregate $edges
* @return Edges
*/
public static function factory($edges)
{
if ($edges instanceof EdgesAggregate) {
return $edges->getEdges();
}
return new self($edges);
}
/**
* create new Edges instance that references the given source array of Edge instances
*
* Any changes in the referenced source array will automatically be
* reflected in this Set of Edges, e.g. if you add an Edge instance to the
* array, it will automatically be included in this Set.
*
* @param array $edgesArray
* @return Edges
*/
public static function factoryArrayReference(array &$edgesArray)
{
$edges = new static();
$edges->edges =& $edgesArray;
return $edges;
}
/**
* instantiate new Set of Edges
*
* @param array $edges
*/
public function __construct(array $edges = array())
{
$this->edges = $edges;
}
/**
* get array index for given Edge
*
* @param Edge $edge
* @throws OutOfBoundsException
* @return mixed
*/
public function getIndexEdge(Edge $edge)
{
$id = array_search($edge, $this->edges, true);
if ($id === false) {
throw new OutOfBoundsException('Given edge does NOT exist');
}
return $id;
}
/**
* return first Edge in this set of Edges
*
* some algorithms do not need a particular edge, but merely a (random)
* starting point. this is a convenience function to just pick the first
* edge from the list of known edges.
*
* @return Edge first Edge in this set of Edges
* @throws UnderflowException if set is empty
* @see self::getEdgeOrder() if you need to apply ordering first
*/
public function getEdgeFirst()
{
if (!$this->edges) {
throw new UnderflowException('Does not contain any edges');
}
reset($this->edges);
return current($this->edges);
}
/**
* return last Edge in this set of Edges
*
* @return Edge last Edge in this set of Edges
* @throws UnderflowException if set is empty
*/
public function getEdgeLast()
{
if (!$this->edges) {
throw new UnderflowException('Does not contain any edges');
}
end($this->edges);
return current($this->edges);
}
/**
* return Edge at given array index
*
* @param mixed $index
* @throws OutOfBoundsException if the given index does not exist
* @return Edge
*/
public function getEdgeIndex($index)
{
if (!isset($this->edges[$index])) {
throw new OutOfBoundsException('Invalid edge index');
}
return $this->edges[$index];
}
/**
* return first Edge that matches the given callback filter function
*
* @param callable $callbackCheck
* @return Edge
* @throws UnderflowException if no Edge matches the given callback filter function
* @uses self::getEdgeMatchOrNull()
* @see self::getEdgesMatch() if you want to return *all* Edges that match
*/
public function getEdgeMatch($callbackCheck)
{
$ret = $this->getEdgeMatchOrNull($callbackCheck);
if ($ret === null) {
throw new UnderflowException('No edge found');
}
return $ret;
}
/**
* checks whethere there's an Edge that matches the given callback filter function
*
* @param callable $callbackCheck
* @return bool
* @see self::getEdgeMatch() to return the Edge instance that matches the given callback filter function
* @uses self::getEdgeMatchOrNull()
*/
public function hasEdgeMatch($callbackCheck)
{
return ($this->getEdgeMatchOrNull($callbackCheck) !== null);
}
/**
* get a new set of Edges that match the given callback filter function
*
* This only keeps Edge elements if the $callbackCheck returns a bool
* true and filters out everything else.
*
* Edge index positions will be left unchanged.
*
* @param callable $callbackCheck
* @return Edges a new Edges instance
* @see self::getEdgeMatch()
*/
public function getEdgesMatch($callbackCheck)
{
return new static(array_filter($this->edges, $callbackCheck));
}
/**
* get new set of Edges ordered by given criterium $orderBy
*
* Edge index positions will be left unchanged.
*
* @param int $orderBy criterium to sort by. see self::ORDER_WEIGHT, etc.
* @param bool $desc whether to return biggest first (true) instead of smallest first (default:false)
* @return Edges a new Edges set ordered by the given $orderBy criterium
* @throws InvalidArgumentException if criterium is unknown
*/
public function getEdgesOrder($orderBy, $desc = false)
{
if ($orderBy === self::ORDER_RANDOM) {
// shuffle the edge positions
$keys = array_keys($this->edges);
shuffle($keys);
// re-order according to shuffled edge positions
$edges = array();
foreach ($keys as $key) {
$edges[$key] = $this->edges[$key];
}
// create iterator for shuffled array (no need to check DESC flag)
return new static($edges);
}
$callback = $this->getCallback($orderBy);
$array = $this->edges;
uasort($array, function (Edge $va, Edge $vb) use ($callback, $desc) {
$ra = $callback($desc ? $vb : $va);
$rb = $callback($desc ? $va : $vb);
if ($ra < $rb) {
return -1;
} elseif ($ra > $rb) {
return 1;
} else {
return 0;
}
});
return new static($array);
}
/**
* get first edge ordered by given criterium $orderBy
*
* @param int $orderBy criterium to sort by. see self::ORDER_WEIGHT, etc.
* @param bool $desc whether to return biggest (true) instead of smallest (default:false)
* @return Edge
* @throws InvalidArgumentException if criterium is unknown
* @throws UnderflowException if no edges exist
*/
public function getEdgeOrder($orderBy, $desc=false)
{
if (!$this->edges) {
throw new UnderflowException('No edge found');
}
// random order
if ($orderBy === self::ORDER_RANDOM) {
// just return by random key (no need to check for DESC flag)
return $this->edges[array_rand($this->edges)];
}
$callback = $this->getCallback($orderBy);
$ret = NULL;
$best = NULL;
foreach ($this->edges as $edge) {
$now = $callback($edge);
if ($ret === NULL || ($desc && $now > $best) || (!$desc && $now < $best)) {
$ret = $edge;
$best = $now;
}
}
return $ret;
}
/**
* return self reference to Set of Edges
*
* @return Edges
* @see self::factory()
*/
public function getEdges()
{
return $this;
}
/**
* get a new set of Edges where each Edge is distinct/unique
*
* @return Edges a new Edges instance
*/
public function getEdgesDistinct()
{
$edges = array();
foreach ($this->edges as $edge) {
// filter duplicate edges
if (!in_array($edge, $edges, true)) {
$edges []= $edge;
}
}
return new Edges($edges);
}
/**
* get intersection of Edges with given other Edges
*
* The intersection contains all Edge instances that are present in BOTH
* this set of Edges and the given set of other Edges.
*
* Edge index/keys will be preserved from original array.
*
* Duplicate Edge instances will be kept if the corresponding number of
* Edge instances is also found in $otherEdges.
*
* @param Edges|Edge[] $otherEdges
* @return Edges a new Edges set
*/
public function getEdgesIntersection($otherEdges)
{
$otherArray = self::factory($otherEdges)->getVector();
$edges = array();
foreach ($this->edges as $eid => $edge) {
$i = array_search($edge, $otherArray, true);
if ($i !== false) {
// remove from other array in order to check for duplicate matches
unset($otherArray[$i]);
$edges[$eid] = $edge;
}
}
return new static($edges);
}
/**
* return array of Edge instances
*
* @return Edge[]
*/
public function getVector()
{
return array_values($this->edges);
}
/**
* count number of Edges
*
* @return int
* @see self::isEmpty()
*/
#[\ReturnTypeWillChange]
public function count()
{
return count($this->edges);
}
/**
* check whether this Set of Edges is empty
*
* A Set if empty if no single Edge instance is added. This is faster
* than calling `count() === 0`.
*
* @return bool
*/
public function isEmpty()
{
return !$this->edges;
}
/**
* get Iterator
*
* This method implements the IteratorAggregate interface and allows this
* Set of Edges to be used in foreach loops.
*
* @return \IteratorIterator
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return new \IteratorIterator(new \ArrayIterator($this->edges));
}
/**
* call given $callback on each Edge and sum their results
*
* @param callable $callback
* @return number
* @throws InvalidArgumentException for invalid callbacks
* @uses self::getCallback()
*/
public function getSumCallback($callback)
{
$callback = $this->getCallback($callback);
// return array_sum(array_map($callback, $this->edges));
$sum = 0;
foreach ($this->edges as $edge) {
$sum += $callback($edge);
}
return $sum;
}
private function getEdgeMatchOrNull($callbackCheck)
{
$callbackCheck = $this->getCallback($callbackCheck);
foreach ($this->edges as $edge) {
if ($callbackCheck($edge)) {
return $edge;
}
}
return null;
}
/**
* get callback/Closure to be called on Edge instances for given callback identifier
*
* @param callable|int $callback
* @throws InvalidArgumentException
* @return callable
*/
private function getCallback($callback)
{
if (is_callable($callback)) {
if (is_array($callback)) {
$callback = function (Edge $edge) use ($callback) {
return call_user_func($callback, $edge);
};
}
return $callback;
}
static $methods = array(
self::ORDER_WEIGHT => 'getWeight',
self::ORDER_CAPACITY => 'getCapacity',
self::ORDER_CAPACITY_REMAINING => 'getCapacityRemaining',
self::ORDER_FLOW => 'getFlow'
);
if (!is_int($callback) || !isset($methods[$callback])) {
throw new InvalidArgumentException('Invalid callback given');
}
$method = $methods[$callback];
return function (Edge $edge) use ($method) {
return $edge->$method();
};
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Fhaculty\Graph\Set;
/**
* A basic interface for every class that provides access to its Set of Edges
*/
interface EdgesAggregate
{
/**
* @return Edges
*/
public function getEdges();
}

531
vendor/clue/graph/src/Set/Vertices.php vendored Normal file
View File

@@ -0,0 +1,531 @@
<?php
namespace Fhaculty\Graph\Set;
use Fhaculty\Graph\Vertex;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Exception\OutOfBoundsException;
use Fhaculty\Graph\Exception\UnderflowException;
/**
* A Set of Vertices
*
* Contains any number of Vertex instances.
*
* The Set is a readonly instance and it provides methods to get single Vertex
* instances or to get a new Set of Vertices. This way it's safe to pass around
* the original Set of Vertices, because it will never be modified.
*/
class Vertices implements \Countable, \IteratorAggregate, VerticesAggregate
{
/**
* order by vertex ID
*
* @var int
* @see Vertex::getId()
*/
const ORDER_ID = 1;
/**
* random/shuffled order
*
* @var int
*/
const ORDER_RANDOM = 5;
/**
* order by vertex group
*
* @var int
* @see Vertex::getGroup()
*/
const ORDER_GROUP = 6;
protected $vertices = array();
/**
* create new Vertices instance
*
* You can pass in just about anything that can be expressed as a Set of
* Vertices, such as:
* - an array of Vertex instances
* - any Algorithm that implements the VerticesAggregate interface
* - a Graph instance or
* - an existing Set of Vertices which will be returned as-is
*
* @param array|Vertices|VerticesAggregate $vertices
* @return Vertices
*/
public static function factory($vertices)
{
if ($vertices instanceof VerticesAggregate) {
return $vertices->getVertices();
}
return new self($vertices);
}
/**
* create new Vertices instance that references the given source array of Vertex instances
*
* Any changes in the referenced source array will automatically be
* reflected in this Set of Vertices, e.g. if you add a Vertex instance to
* the array, it will automatically be included in this Set.
*
* @param array $verticesArray
* @return Vertices
*/
public static function factoryArrayReference(array &$verticesArray)
{
$vertices = new static();
$vertices->vertices =& $verticesArray;
return $vertices;
}
/**
* instantiate new Set of Vertices
*
* @param array $vertices
*/
public function __construct(array $vertices = array())
{
$this->vertices = $vertices;
}
/**
* get Vertex with the given vertex $id
*
* @param int|string $id
* @return Vertex
* @throws OutOfBoundsException if no Vertex with the given ID exists
* @uses self::getVertexMatch()
*/
public function getVertexId($id)
{
try {
return $this->getVertexMatch($this->getCallbackId($id));
}
catch (UnderflowException $e) {
throw new OutOfBoundsException('Vertex ' . $id . ' does not exist', 0, $e);
}
}
/**
* checks whether given vertex ID exists in this set of vertices
*
* @param int|string $id identifier of Vertex
* @return bool
* @uses self::hasVertexMatch()
*/
public function hasVertexId($id)
{
return $this->hasVertexMatch($this->getCallbackId($id));
}
/**
* get array index for given Vertex
*
* not every set of Vertices represents a map, as such array index and
* Vertex ID do not necessarily have to match.
*
* @param Vertex $vertex
* @throws OutOfBoundsException
* @return mixed
*/
public function getIndexVertex(Vertex $vertex)
{
$id = array_search($vertex, $this->vertices, true);
if ($id === false) {
throw new OutOfBoundsException('Given vertex does NOT exist');
}
return $id;
}
/**
* return first Vertex in this set of Vertices
*
* some algorithms do not need a particular vertex, but merely a (random)
* starting point. this is a convenience function to just pick the first
* vertex from the list of known vertices.
*
* @return Vertex first Vertex in this set of Vertices
* @throws UnderflowException if set is empty
* @see self::getVertexOrder() if you need to apply ordering first
*/
public function getVertexFirst()
{
if (!$this->vertices) {
throw new UnderflowException('Does not contain any vertices');
}
reset($this->vertices);
return current($this->vertices);
}
/**
* return last Vertex in this set of Vertices
*
* @return Vertex last Vertex in this set of Vertices
* @throws UnderflowException if set is empty
*/
public function getVertexLast()
{
if (!$this->vertices) {
throw new UnderflowException('Does not contain any vertices');
}
end($this->vertices);
return current($this->vertices);
}
/**
* return first Vertex that matches the given callback filter function
*
* @param callable $callbackCheck
* @return Vertex
* @throws UnderflowException if no Vertex matches the given callback filter function
* @uses self::getVertexMatchOrNull()
* @see self::getVerticesMatch() if you want to return *all* Vertices that match
*/
public function getVertexMatch($callbackCheck)
{
$ret = $this->getVertexMatchOrNull($callbackCheck);
if ($ret === null) {
throw new UnderflowException('No vertex found');
}
return $ret;
}
/**
* checks whether there's a Vertex that matches the given callback filter function
*
* @param callable $callbackCheck
* @return bool
* @see self::getVertexMatch() to return the Vertex instance that matches the given callback filter function
* @uses self::getVertexMatchOrNull()
*/
public function hasVertexMatch($callbackCheck)
{
return ($this->getVertexMatchOrNull($callbackCheck) !== null);
}
/**
* get a new set of Vertices that match the given callback filter function
*
* This only keeps Vertex elements if the $callbackCheck returns a bool
* true and filters out everything else.
*
* Vertex index positions will be left unchanged, so if you call this method
* on a VerticesMap, it will also return a VerticesMap.
*
* @param callable $callbackCheck
* @return Vertices a new Vertices instance
* @see self::getVertexMatch()
*/
public function getVerticesMatch($callbackCheck)
{
return new static(array_filter($this->vertices, $callbackCheck));
}
/**
* get new Set of Vertices ordered by given criterium $orderBy
*
* Vertex index positions will be left unchanged, so if you call this method
* on a VerticesMap, it will also return a VerticesMap.
*
* @param int $orderBy criterium to sort by. see Vertex::ORDER_ID, etc.
* @param bool $desc whether to return biggest first (true) instead of smallest first (default:false)
* @return Vertices a new Vertices set ordered by the given $orderBy criterium
* @throws InvalidArgumentException if criterium is unknown
* @see self::getVertexOrder()
*/
public function getVerticesOrder($orderBy, $desc = false)
{
if ($orderBy === self::ORDER_RANDOM) {
// shuffle the vertex positions
$keys = array_keys($this->vertices);
shuffle($keys);
// re-order according to shuffled vertex positions
$vertices = array();
foreach ($keys as $key) {
$vertices[$key] = $this->vertices[$key];
}
// create iterator for shuffled array (no need to check DESC flag)
return new static($vertices);
}
$callback = $this->getCallback($orderBy);
$array = $this->vertices;
uasort($array, function (Vertex $va, Vertex $vb) use ($callback, $desc) {
$ra = $callback($desc ? $vb : $va);
$rb = $callback($desc ? $va : $vb);
if ($ra < $rb) {
return -1;
} elseif ($ra > $rb) {
return 1;
} else {
return 0;
}
});
return new static($array);
}
/**
* get intersection of Vertices with given other Vertices
*
* The intersection contains all Vertex instances that are present in BOTH
* this set of Vertices and the given set of other Vertices.
*
* Vertex index/keys will be preserved from original array.
*
* Duplicate Vertex instances will be kept if the corresponding number of
* Vertex instances is also found in $otherVertices.
*
* @param Vertices|Vertex[] $otherVertices
* @return Vertices a new Vertices set
*/
public function getVerticesIntersection($otherVertices)
{
$otherArray = self::factory($otherVertices)->getVector();
$vertices = array();
foreach ($this->vertices as $vid => $vertex) {
$i = array_search($vertex, $otherArray, true);
if ($i !== false) {
// remove from other array in order to check for duplicate matches
unset($otherArray[$i]);
$vertices[$vid] = $vertex;
}
}
return new static($vertices);
}
/**
* get first vertex (optionally ordered by given criterium $by) from given array of vertices
*
* @param int $orderBy criterium to sort by. see Vertex::ORDER_ID, etc.
* @param bool $desc whether to return biggest (true) instead of smallest (default:false)
* @return Vertex
* @throws InvalidArgumentException if criterium is unknown
* @throws UnderflowException if no vertices exist
* @see self::getVerticesOrder()
*/
public function getVertexOrder($orderBy, $desc=false)
{
if (!$this->vertices) {
throw new UnderflowException('No vertex found');
}
// random order
if ($orderBy === self::ORDER_RANDOM) {
// just return by random key (no need to check for DESC flag)
return $this->vertices[array_rand($this->vertices)];
}
$callback = $this->getCallback($orderBy);
$ret = NULL;
$best = NULL;
foreach ($this->vertices as $vertex) {
$now = $callback($vertex);
if ($ret === NULL || ($desc && $now > $best) || (!$desc && $now < $best)) {
$ret = $vertex;
$best = $now;
}
}
return $ret;
}
/**
* return self reference to Set of Vertices
*
* @return Vertices
* @see self::factory()
*/
public function getVertices()
{
return $this;
}
/**
* get a new set of Vertices where each Vertex is distinct/unique
*
* @return VerticesMap a new VerticesMap instance
* @uses self::getMap()
*/
public function getVerticesDistinct()
{
return new VerticesMap($this->getMap());
}
/**
* get a mapping array of Vertex ID => Vertex instance and thus remove duplicate vertices
*
* @return Vertex[] Vertex ID => Vertex instance
* @uses Vertex::getId()
*/
public function getMap()
{
$vertices = array();
foreach ($this->vertices as $vertex) {
$vertices[$vertex->getId()] = $vertex;
}
return $vertices;
}
/**
* return array of Vertex IDs
*
* @return array
*/
public function getIds()
{
$ids = array();
foreach ($this->vertices as $vertex) {
$ids []= $vertex->getId();
}
return $ids;
}
/**
* return array of Vertex instances
*
* @return Vertex[]
*/
public function getVector()
{
return array_values($this->vertices);
}
/**
* count number of vertices
*
* @return int
* @see self::isEmpty()
*/
#[\ReturnTypeWillChange]
public function count()
{
return count($this->vertices);
}
/**
* check whether this Set of Vertices is empty
*
* A Set if empty if no single Vertex instance is added. This is faster
* than calling `count() === 0`.
*
* @return bool
*/
public function isEmpty()
{
return !$this->vertices;
}
/**
* check whether this set contains any duplicate vertex instances
*
* @return bool
* @uses self::getMap()
*/
public function hasDuplicates()
{
return (count($this->vertices) !== count($this->getMap()));
}
/**
* get Iterator
*
* This method implements the IteratorAggregate interface and allows this
* Set of Vertices to be used in foreach loops.
*
* @return \IteratorIterator
*/
#[\ReturnTypeWillChange]
public function getIterator()
{
return new \IteratorIterator(new \ArrayIterator($this->vertices));
}
/**
* call given $callback on each Vertex and sum their results
*
* @param callable $callback
* @return number
* @throws InvalidArgumentException for invalid callbacks
* @uses self::getCallback()
*/
public function getSumCallback($callback)
{
$callback = $this->getCallback($callback);
// return array_sum(array_map($callback, $this->vertices));
$sum = 0;
foreach ($this->vertices as $vertex) {
$sum += $callback($vertex);
}
return $sum;
}
private function getCallbackId($id)
{
return function (Vertex $vertex) use ($id) {
return ($vertex->getId() == $id);
};
}
private function getVertexMatchOrNull($callbackCheck)
{
$callbackCheck = $this->getCallback($callbackCheck);
foreach ($this->vertices as $vertex) {
if ($callbackCheck($vertex)) {
return $vertex;
}
}
return null;
}
/**
* get callback/Closure to be called on Vertex instances for given callback identifier
*
* @param callable|int $callback
* @throws InvalidArgumentException
* @return callable
*/
private function getCallback($callback)
{
if (is_callable($callback)) {
if (is_array($callback)) {
$callback = function (Vertex $vertex) use ($callback) {
return call_user_func($callback, $vertex);
};
}
return $callback;
}
static $methods = array(
self::ORDER_ID => 'getId',
self::ORDER_GROUP => 'getGroup'
);
if (!is_int($callback) || !isset($methods[$callback])) {
throw new InvalidArgumentException('Invalid callback given');
}
$method = $methods[$callback];
return function (Vertex $vertex) use ($method) {
return $vertex->$method();
};
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Fhaculty\Graph\Set;
/**
* Basic interface for every class that provides access to its Set of Vertices
*/
interface VerticesAggregate
{
/**
* @return Vertices
*/
public function getVertices();
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Fhaculty\Graph\Set;
use Fhaculty\Graph\Exception\OutOfBoundsException;
use Fhaculty\Graph\Vertex;
/**
* A set of Vertices that are already stored in a vertex ID => Vertex instance mapping array
*
* Among others, using a mapped array significantly speeds up accessing vertices
* by ID. However, there's no way to store multiple vertices with the same ID
* (i.e. each Vertex ID has to be unique).
*/
class VerticesMap extends Vertices
{
public function getMap()
{
return $this->vertices;
}
public function getVertexId($id)
{
if (!isset($this->vertices[$id])) {
throw new OutOfBoundsException('Invalid vertex ID');
}
return $this->vertices[$id];
}
public function hasVertexId($id)
{
return isset($this->vertices[$id]);
}
public function getVerticesDistinct()
{
return $this;
}
public function getIds()
{
return array_keys($this->vertices);
}
public function getIndexVertex(Vertex $vertex)
{
$id = $vertex->getId();
if (!isset($this->vertices[$id]) || $this->vertices[$id] !== $vertex) {
throw new OutOfBoundsException();
}
return $id;
}
/**
*
* @return VerticesMap
*/
public function getVertices()
{
return $this;
}
public function hasDuplicates()
{
return false;
}
}

406
vendor/clue/graph/src/Vertex.php vendored Normal file
View File

@@ -0,0 +1,406 @@
<?php
namespace Fhaculty\Graph;
use Fhaculty\Graph\Attribute\AttributeAware;
use Fhaculty\Graph\Attribute\AttributeBagReference;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Edge\Directed as EdgeDirected;
use Fhaculty\Graph\Edge\Undirected as EdgeUndirected;
use Fhaculty\Graph\Exception\BadMethodCallException;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Set\EdgesAggregate;
use Fhaculty\Graph\Set\Vertices;
class Vertex implements EdgesAggregate, AttributeAware
{
private $id;
/**
* @var Edge[]
*/
private $edges = array();
/**
* @var Graph
*/
private $graph;
/**
* vertex balance
*
* @var int|float|NULL
* @see Vertex::setBalance()
*/
private $balance;
/**
* group number
*
* @var int
* @see Vertex::setGroup()
*/
private $group = 0;
private $attributes = array();
/**
* Create a new Vertex
*
* @param Graph $graph graph to be added to
* @param string|int $id identifier used to uniquely identify this vertex in the graph
* @see Graph::createVertex() to create new vertices
*/
public function __construct(Graph $graph, $id)
{
if (!is_int($id) && !is_string($id)) {
throw new InvalidArgumentException('Vertex ID has to be of type integer or string');
}
$this->id = $id;
$this->graph = $graph;
$graph->addVertex($this);
}
/**
* get graph this vertex is attached to
*
* @return Graph
*/
public function getGraph()
{
return $this->graph;
}
public function getBalance()
{
return $this->balance;
}
public function setBalance($balance)
{
if ($balance !== NULL && !is_float($balance) && !is_int($balance)) {
throw new InvalidArgumentException('Invalid balance given - must be numeric');
}
$this->balance = $balance;
return $this;
}
/**
* set group number of this vertex
*
* @param int $group
* @return Vertex $this (chainable)
* @throws InvalidArgumentException if group is not numeric
*/
public function setGroup($group)
{
if (!is_int($group)) {
throw new InvalidArgumentException('Invalid group number');
}
$this->group = $group;
return $this;
}
/**
* get group number
*
* @return int
*/
public function getGroup()
{
return $this->group;
}
/**
* returns id of this Vertex
*
* @return int|string
*/
public function getId()
{
return $this->id;
}
/**
* create new directed edge from this start vertex to given target vertex
*
* @param Vertex $vertex target vertex
* @return EdgeDirected
* @throws InvalidArgumentException
* @uses Graph::addEdge()
*/
public function createEdgeTo(Vertex $vertex)
{
return new EdgeDirected($this, $vertex);
}
/**
* add new undirected (bidirectional) edge between this vertex and given vertex
*
* @param Vertex $vertex
* @return EdgeUndirected
* @throws InvalidArgumentException
* @uses Graph::addEdge()
*/
public function createEdge(Vertex $vertex)
{
return new EdgeUndirected($this, $vertex);
}
/**
* add the given edge to list of connected edges (MUST NOT be called manually)
*
* @param Edge $edge
* @return void
* @internal
* @see self::createEdge() instead!
*/
public function addEdge(Edge $edge)
{
$this->edges[] = $edge;
}
/**
* remove the given edge from list of connected edges (MUST NOT be called manually)
*
* @param Edge $edge
* @return void
* @throws InvalidArgumentException if given edge does not exist
* @internal
* @see Edge::destroy() instead!
*/
public function removeEdge(Edge $edge)
{
$id = array_search($edge, $this->edges, true);
if ($id === false) {
throw new InvalidArgumentException('Given edge does NOT exist');
}
unset($this->edges[$id]);
}
/**
* check whether this vertex has a direct edge to given $vertex
*
* @param Vertex $vertex
* @return bool
* @uses Edge::hasVertexTarget()
*/
public function hasEdgeTo(Vertex $vertex)
{
$that = $this;
return $this->getEdges()->hasEdgeMatch(function (Edge $edge) use ($that, $vertex) {
return $edge->isConnection($that, $vertex);
});
}
/**
* check whether the given vertex has a direct edge to THIS vertex
*
* @param Vertex $vertex
* @return bool
* @uses Vertex::hasEdgeTo()
*/
public function hasEdgeFrom(Vertex $vertex)
{
return $vertex->hasEdgeTo($this);
}
/**
* get set of ALL Edges attached to this vertex
*
* @return Edges
*/
public function getEdges()
{
return new Edges($this->edges);
}
/**
* get set of all outgoing Edges attached to this vertex
*
* @return Edges
*/
public function getEdgesOut()
{
$that = $this;
$prev = null;
return $this->getEdges()->getEdgesMatch(function (Edge $edge) use ($that, &$prev) {
$ret = $edge->hasVertexStart($that);
// skip duplicate directed loop edges
if ($edge === $prev && $edge instanceof EdgeDirected) {
$ret = false;
}
$prev = $edge;
return $ret;
});
}
/**
* get set of all ingoing Edges attached to this vertex
*
* @return Edges
*/
public function getEdgesIn()
{
$that = $this;
$prev = null;
return $this->getEdges()->getEdgesMatch(function (Edge $edge) use ($that, &$prev) {
$ret = $edge->hasVertexTarget($that);
// skip duplicate directed loop edges
if ($edge === $prev && $edge instanceof EdgeDirected) {
$ret = false;
}
$prev = $edge;
return $ret;
});
}
/**
* get set of Edges FROM this vertex TO the given vertex
*
* @param Vertex $vertex
* @return Edges
* @uses Edge::hasVertexTarget()
*/
public function getEdgesTo(Vertex $vertex)
{
$that = $this;
return $this->getEdges()->getEdgesMatch(function (Edge $edge) use ($that, $vertex) {
return $edge->isConnection($that, $vertex);
});
}
/**
* get set of Edges FROM the given vertex TO this vertex
*
* @param Vertex $vertex
* @return Edges
* @uses Vertex::getEdgesTo()
*/
public function getEdgesFrom(Vertex $vertex)
{
return $vertex->getEdgesTo($this);
}
/**
* get set of adjacent Vertices of this vertex (edge FROM or TO this vertex)
*
* If there are multiple parallel edges between the same Vertex, it will be
* returned several times in the resulting Set of Vertices. If you only
* want unique Vertex instances, use `getVerticesDistinct()`.
*
* @return Vertices
* @uses Edge::hasVertexStart()
* @uses Edge::getVerticesToFrom()
* @uses Edge::getVerticesFromTo()
*/
public function getVerticesEdge()
{
$ret = array();
foreach ($this->edges as $edge) {
if ($edge->hasVertexStart($this)) {
$ret []= $edge->getVertexToFrom($this);
} else {
$ret []= $edge->getVertexFromTo($this);
}
}
return new Vertices($ret);
}
/**
* get set of all Vertices this vertex has an edge to
*
* If there are multiple parallel edges to the same Vertex, it will be
* returned several times in the resulting Set of Vertices. If you only
* want unique Vertex instances, use `getVerticesDistinct()`.
*
* @return Vertices
* @uses Vertex::getEdgesOut()
* @uses Edge::getVerticesToFrom()
*/
public function getVerticesEdgeTo()
{
$ret = array();
foreach ($this->getEdgesOut() as $edge) {
$ret []= $edge->getVertexToFrom($this);
}
return new Vertices($ret);
}
/**
* get set of all Vertices that have an edge TO this vertex
*
* If there are multiple parallel edges from the same Vertex, it will be
* returned several times in the resulting Set of Vertices. If you only
* want unique Vertex instances, use `getVerticesDistinct()`.
*
* @return Vertices
* @uses Vertex::getEdgesIn()
* @uses Edge::getVerticesFromTo()
*/
public function getVerticesEdgeFrom()
{
$ret = array();
foreach ($this->getEdgesIn() as $edge) {
$ret []= $edge->getVertexFromTo($this);
}
return new Vertices($ret);
}
/**
* destroy vertex and all edges connected to it and remove reference from graph
*
* @uses Edge::destroy()
* @uses Graph::removeVertex()
*/
public function destroy()
{
foreach ($this->getEdges()->getEdgesDistinct() as $edge) {
$edge->destroy();
}
$this->graph->removeVertex($this);
}
/**
* do NOT allow cloning of objects
*
* @throws BadMethodCallException
*/
private function __clone()
{
// @codeCoverageIgnoreStart
throw new BadMethodCallException();
// @codeCoverageIgnoreEnd
}
public function getAttribute($name, $default = null)
{
return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
}
public function setAttribute($name, $value)
{
$this->attributes[$name] = $value;
}
public function getAttributeBag()
{
return new AttributeBagReference($this->attributes);
}
}

311
vendor/clue/graph/src/Walk.php vendored Normal file
View File

@@ -0,0 +1,311 @@
<?php
namespace Fhaculty\Graph;
use Fhaculty\Graph\Set\Edges;
use Fhaculty\Graph\Set\Vertices;
use Fhaculty\Graph\Edge\Base as Edge;
use Fhaculty\Graph\Exception\UnderflowException;
use Fhaculty\Graph\Exception\InvalidArgumentException;
use Fhaculty\Graph\Set\DualAggregate;
/**
* Base Walk class
*
* The general term "Walk" bundles the following mathematical concepts:
* walk, path, cycle, circuit, loop, trail, tour, etc.
*
* @link http://en.wikipedia.org/wiki/Path_%28graph_theory%29
* @link http://en.wikipedia.org/wiki/Glossary_of_graph_theory#Walks
* @see Fhaculty\Graph\Algorithm\Property\WalkProperty for checking special cases, such as cycles, loops, closed trails, etc.
*/
class Walk implements DualAggregate
{
/**
* construct new walk from given start vertex and given array of edges
*
* @param Edges|Edge[] $edges
* @param Vertex $startVertex
* @return Walk
*/
public static function factoryFromEdges($edges, Vertex $startVertex)
{
$vertices = array($startVertex);
$vertexCurrent = $startVertex;
foreach ($edges as $edge) {
$vertexCurrent = $edge->getVertexToFrom($vertexCurrent);
$vertices []= $vertexCurrent;
}
return new self($vertices, $edges);
}
/**
* create new walk instance between given set of Vertices / array of Vertex instances
*
* @param Vertices|Vertex[] $vertices
* @param int|null $by
* @param bool $desc
* @return Walk
* @throws UnderflowException if no vertices were given
* @see Edges::getEdgeOrder() for parameters $by and $desc
*/
public static function factoryFromVertices($vertices, $by = null, $desc = false)
{
$edges = array();
$last = NULL;
foreach ($vertices as $vertex) {
// skip first vertex as last is unknown
if ($last !== NULL) {
// pick edge between last vertex and this vertex
/* @var $last Vertex */
if ($by === null) {
$edges []= $last->getEdgesTo($vertex)->getEdgeFirst();
} else {
$edges []= $last->getEdgesTo($vertex)->getEdgeOrder($by, $desc);
}
}
$last = $vertex;
}
if ($last === NULL) {
throw new UnderflowException('No vertices given');
}
return new self($vertices, $edges);
}
/**
* create new cycle instance from given predecessor map
*
* @param Vertex[] $predecessors map of vid => predecessor vertex instance
* @param Vertex $vertex start vertex to search predecessors from
* @param int|null $by
* @param bool $desc
* @return Walk
* @throws UnderflowException
* @see Edges::getEdgeOrder() for parameters $by and $desc
* @uses self::factoryFromVertices()
*/
public static function factoryCycleFromPredecessorMap(array $predecessors, Vertex $vertex, $by = null, $desc = false)
{
// find a vertex in the cycle
$vid = $vertex->getId();
$startVertices = array();
do {
if (!isset($predecessors[$vid])) {
throw new InvalidArgumentException('Predecessor map is incomplete and does not form a cycle');
}
$startVertices[$vid] = $vertex;
$vertex = $predecessors[$vid];
$vid = $vertex->getId();
} while (!isset($startVertices[$vid]));
// find negative cycle
$vid = $vertex->getId();
// build array of vertices in cycle
$vertices = array();
do {
// add new vertex to cycle
$vertices[$vid] = $vertex;
// get predecessor of vertex
$vertex = $predecessors[$vid];
$vid = $vertex->getId();
// continue until we find a vertex that's already in the circle (i.e. circle is closed)
} while (!isset($vertices[$vid]));
// reverse cycle, because cycle is actually built in opposite direction due to checking predecessors
$vertices = array_reverse($vertices, true);
// additional edge from last vertex to first vertex
$vertices[] = reset($vertices);
return self::factoryCycleFromVertices($vertices, $by, $desc);
}
/**
* create new cycle instance with edges between given vertices
*
* @param Vertex[]|Vertices $vertices
* @param int|null $by
* @param bool $desc
* @return Walk
* @throws UnderflowException if no vertices were given
* @see Edges::getEdgeOrder() for parameters $by and $desc
* @uses self::factoryFromVertices()
*/
public static function factoryCycleFromVertices($vertices, $by = null, $desc = false)
{
$cycle = self::factoryFromVertices($vertices, $by, $desc);
if ($cycle->getEdges()->isEmpty()) {
throw new InvalidArgumentException('Cycle with no edges can not exist');
}
if ($cycle->getVertices()->getVertexFirst() !== $cycle->getVertices()->getVertexLast()) {
throw new InvalidArgumentException('Cycle has to start and end at the same vertex');
}
return $cycle;
}
/**
* create new cycle instance with vertices connected by given edges
*
* @param Edges|Edge[] $edges
* @param Vertex $startVertex
* @return Walk
* @throws InvalidArgumentException if the given array of edges does not represent a valid cycle
* @uses self::factoryFromEdges()
*/
public static function factoryCycleFromEdges($edges, Vertex $startVertex)
{
$cycle = self::factoryFromEdges($edges, $startVertex);
// ensure this walk is actually a cycle by checking start = end
if ($cycle->getVertices()->getVertexLast() !== $startVertex) {
throw new InvalidArgumentException('The given array of edges does not represent a cycle');
}
return $cycle;
}
/**
*
* @var Vertices
*/
protected $vertices;
/**
*
* @var Edges
*/
protected $edges;
protected function __construct($vertices, $edges)
{
$this->vertices = Vertices::factory($vertices);
$this->edges = Edges::factory($edges);
}
/**
* return original graph
*
* @return Graph
* @uses self::getVertices()
* @uses Vertices::getVertexFirst()
* @uses Vertex::getGraph()
*/
public function getGraph()
{
return $this->getVertices()->getVertexFirst()->getGraph();
}
/**
* create new graph clone with only vertices and edges actually in the walk
*
* do not add duplicate vertices and edges for loops and intersections, etc.
*
* @return Graph
* @uses Walk::getEdges()
* @uses Graph::createGraphCloneEdges()
*/
public function createGraph()
{
// create new graph clone with only edges of walk
$graph = $this->getGraph()->createGraphCloneEdges($this->getEdges());
$vertices = $this->getVertices()->getMap();
// get all vertices
foreach ($graph->getVertices()->getMap() as $vid => $vertex) {
if (!isset($vertices[$vid])) {
// remove those not present in the walk (isolated vertices, etc.)
$vertex->destroy();
}
}
return $graph;
}
/**
* return set of all Edges of walk (in sequence visited in walk, may contain duplicates)
*
* If you need to return set a of all unique Edges of walk, use
* `Walk::getEdges()->getEdgesDistinct()` instead.
*
* @return Edges
*/
public function getEdges()
{
return $this->edges;
}
/**
* return set of all Vertices of walk (in sequence visited in walk, may contain duplicates)
*
* If you need to return set a of all unique Vertices of walk, use
* `Walk::getVertices()->getVerticesDistinct()` instead.
*
* If you need to return the source vertex (first vertex of walk), use
* `Walk::getVertices()->getVertexFirst()` instead.
*
* If you need to return the target/destination vertex (last vertex of walk), use
* `Walk::getVertices()->getVertexLast()` instead.
*
* @return Vertices
*/
public function getVertices()
{
return $this->vertices;
}
/**
* get alternating sequence of vertex, edge, vertex, edge, ..., vertex
*
* @return array
*/
public function getAlternatingSequence()
{
$edges = $this->edges->getVector();
$vertices = $this->vertices->getVector();
$ret = array();
for ($i = 0, $l = count($this->edges); $i < $l; ++$i) {
$ret []= $vertices[$i];
$ret []= $edges[$i];
}
$ret[] = $vertices[$i];
return $ret;
}
/**
* check to make sure this walk is still valid (i.e. source graph still contains all vertices and edges)
*
* @return bool
* @uses Walk::getGraph()
* @uses Graph::getVertices()
* @uses Graph::getEdges()
*/
public function isValid()
{
$vertices = $this->getGraph()->getVertices()->getMap();
// check source graph contains all vertices
foreach ($this->getVertices()->getMap() as $vid => $vertex) {
// make sure vertex ID exists and has not been replaced
if (!isset($vertices[$vid]) || $vertices[$vid] !== $vertex) {
return false;
}
}
$edges = $this->getGraph()->getEdges()->getVector();
// check source graph contains all edges
foreach ($this->edges as $edge) {
if (!in_array($edge, $edges, true)) {
return false;
}
}
return true;
}
}