Commit fb091cd6 authored by Dries's avatar Dries

Issue #2098375 by sdboyer: Add gliph to composer.

parent 27dc2b49
......@@ -4,6 +4,7 @@
"type": "drupal-core",
"license": "GPL-2.0+",
"require": {
"sdboyer/gliph": "0.1.*",
"symfony/class-loader": "2.3.*",
"symfony/dependency-injection": "2.3.*",
"symfony/event-dispatcher": "2.3.*",
......
......@@ -3,7 +3,7 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
"hash": "204b3755db988998bcc618e71b2f235c",
"hash": "4c727150110aeae10efe10a92ff256f5",
"packages": [
{
"name": "doctrine/annotations",
......@@ -1111,6 +1111,50 @@
],
"time": "2012-12-21 11:40:51"
},
{
"name": "sdboyer/gliph",
"version": "0.1.4",
"source": {
"type": "git",
"url": "https://github.com/sdboyer/gliph.git",
"reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sdboyer/gliph/zipball/aad932ef7d808105341cc9a36538e9fe2cb5ee82",
"reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"type": "library",
"autoload": {
"psr-0": {
"Gliph": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sam Boyer",
"email": "tech@samboyer.org"
}
],
"description": "A graph library for PHP.",
"homepage": "http://github.com/sdboyer/gliph",
"keywords": [
"gliph",
"graph",
"library",
"php",
"spl"
],
"time": "2013-09-27 01:15:21"
},
{
"name": "symfony-cmf/routing",
"version": "1.1.0-beta1",
......
......@@ -4,4 +4,4 @@
require_once __DIR__ . '/composer' . '/autoload_real.php';
return ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9::getLoader();
return ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2::getLoader();
......@@ -28,6 +28,7 @@
'Guzzle\\Parser' => array($vendorDir . '/guzzle/parser'),
'Guzzle\\Http' => array($vendorDir . '/guzzle/http'),
'Guzzle\\Common' => array($vendorDir . '/guzzle/common'),
'Gliph' => array($vendorDir . '/sdboyer/gliph/src'),
'EasyRdf_' => array($vendorDir . '/easyrdf/easyrdf/lib'),
'Drupal\\Driver' => array($baseDir . '/drivers/lib'),
'Drupal\\Core' => array($baseDir . '/core/lib'),
......
......@@ -2,7 +2,7 @@
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9
class ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2
{
private static $loader;
......@@ -19,9 +19,9 @@ public static function getLoader()
return self::$loader;
}
spl_autoload_register(array('ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9', 'loadClassLoader'), true, true);
spl_autoload_register(array('ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
spl_autoload_unregister(array('ComposerAutoloaderInit0b93b0210b8b39c2a0b13410cd082de9', 'loadClassLoader'));
spl_autoload_unregister(array('ComposerAutoloaderInit7ffa68419492d19fe654de54c86ae5d2', 'loadClassLoader'));
$vendorDir = dirname(__DIR__);
$baseDir = dirname(dirname($vendorDir));
......
......@@ -2064,5 +2064,51 @@
],
"description": "Symfony Process Component",
"homepage": "http://symfony.com"
},
{
"name": "sdboyer/gliph",
"version": "0.1.4",
"version_normalized": "0.1.4.0",
"source": {
"type": "git",
"url": "https://github.com/sdboyer/gliph.git",
"reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sdboyer/gliph/zipball/aad932ef7d808105341cc9a36538e9fe2cb5ee82",
"reference": "aad932ef7d808105341cc9a36538e9fe2cb5ee82",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"time": "2013-09-27 01:15:21",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"Gliph": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Sam Boyer",
"email": "tech@samboyer.org"
}
],
"description": "A graph library for PHP.",
"homepage": "http://github.com/sdboyer/gliph",
"keywords": [
"gliph",
"graph",
"library",
"php",
"spl"
]
}
]
# Gliph
[![Build Status](https://travis-ci.org/sdboyer/gliph.png?branch=php53)](https://travis-ci.org/sdboyer/gliph)
[![Latest Stable Version](https://poser.pugx.org/sdboyer/gliph/v/stable.png)](https://packagist.org/packages/sdboyer/gliph)
Gliph is a **g**raph **li**brary for **PH**P. It provides graph building blocks and datastructures for use by other PHP applications. It is (currently) designed for use with in-memory graphs, not for interaction with a graph database like [Neo4J](http://neo4j.org/).
Gliph is designed with performance in mind, but primarily to provide a sane interface. Graphs are hard enough without an arcane API making it worse.
## Core Concepts
Gliph has several components that work together: graph classes, algorithms, and visitors. Generally speaking, Gliph is patterned after the [C++ Boost Graph Library](http://www.boost.org/libs/graph/doc); reading their documentation can yield a lot of insight into how Gliph is intended to work.
Note that Gliph is currently written for compatibility with PHP 5.3, but it is intended to port the library to PHP 5.5. The availability of traits, non-scalar/object keys returnable from iterators, and generators will considerably change both the internal and public-facing implementations.
### Graphs
There are a number of different strategies for representing graphs; these strategies are more or less efficient depending on certain properties the graph, and what needs to be done to the graph. The approach taken in Gliph is to offer a roughly consistent 'Graph' interface that is common to all these different strategies. The strategies will have varying levels of efficiency at meeting this common interface, so it is the responsibility of the user to select a graph implementation that is appropriate for their use case. This approach draws heavily from the [taxonomy of graphs](http://www.boost.org/doc/libs/1_54_0/libs/graph/doc/graph_concepts.html) established by the BGL.
Gliph currently implements only an adjacency list graph strategy, in both directed and undirected flavors. Adjacency lists offer efficient access to out-edges, but inefficient access to in-edges (in a directed graph - in an undirected graph, in-edges and out-edges are the same). Adjacency lists and are generally more space-efficient for sparse graphs.
## TODOs
Lots. But, to start with:
- Port to, or provide a parallel implementation in, PHP 5.5. Generators and non-scalar keys from iterators make this all SO much better. In doing that, also shift as much over to traits as possible.
- Implement a generic breadth-first algorithm and its corresponding visitors.
- Implement a generic iterative deepening depth-first algorithm, and its corresponding visitors.
- Implement other popular connected components algorithms, as well as some shortest path algorithms (starting with Dijkstra)
- Write up some examples showing how to actually use the library.
## Acknowledgements
This library draws heavy inspiration from the [C++ Boost Graph Library](http://www.boost.org/libs/graph/doc).
## License
MIT
{
"name": "sdboyer/gliph",
"description": "A graph library for PHP.",
"license": "MIT",
"keywords": ["gliph", "library", "php", "spl", "graph"],
"homepage": "http://github.com/sdboyer/gliph",
"type": "library",
"authors": [
{
"name": "Sam Boyer",
"email": "tech@samboyer.org"
}
],
"require": {
"php": ">=5.3"
},
"autoload": {
"psr-0": { "Gliph": "src/" }
}
}
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="PHPUnit">
<directory>tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory>src/Gliph</directory>
<exclude>
<file>src/Gliph/Visitor/DepthFirstNoOpVisitor.php</file>
</exclude>
</whitelist>
</filter>
<logging>
<log
type="coverage-html"
target="build/coverage"
charset="UTF-8"
yui="true"
highlight="true"
lowUpperBound="35"
highLowerBound="70"
showUncoveredFiles="true"
/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
</phpunit>
<?php
namespace Gliph\Algorithm;
use Gliph\Graph\DirectedGraph;
use Gliph\Visitor\TarjanSCCVisitor;
/**
* Contains algorithms for discovering connected components.
*/
class ConnectedComponent {
/**
* Finds connected components in the provided directed graph.
*
* @param DirectedGraph $graph
* The DirectedGraph to search for connected components.
* @param TarjanSCCVisitor $visitor
* The visitor that will collect and store the connected components. One
* will be created if not provided.
*
* @return TarjanSCCVisitor
* The finalized visitor.
*/
public static function tarjan_scc(DirectedGraph $graph, TarjanSCCVisitor $visitor = NULL) {
$visitor = $visitor ?: new TarjanSCCVisitor();
$counter = 0;
$stack = array();
$indices = new \SplObjectStorage();
$lowlimits = new \SplObjectStorage();
$visit = function($vertex) use (&$visit, &$counter, $graph, &$stack, $indices, $lowlimits, $visitor) {
$indices->attach($vertex, $counter);
$lowlimits->attach($vertex, $counter);
$stack[] = $vertex;
$counter++;
$graph->eachAdjacent($vertex, function ($to) use (&$visit, $vertex, $indices, $lowlimits, &$stack) {
if (!$indices->contains($to)) {
$visit($to);
$lowlimits[$vertex] = min($lowlimits[$vertex], $lowlimits[$to]);
}
else if (in_array($to, $stack)) {
$lowlimits[$vertex] = min($lowlimits[$vertex], $indices[$to]);
}
});
if ($lowlimits[$vertex] === $indices[$vertex]) {
$visitor->newComponent();
do {
$other = array_pop($stack);
$visitor->addToCurrentComponent($other);
} while ($other != $vertex);
}
};
$graph->eachVertex(function($vertex) use (&$visit, $indices) {
if (!$indices->contains($vertex)) {
$visit($vertex);
}
});
return $visitor;
}
}
\ No newline at end of file
<?php
/**
* @file
* Contains \Gliph\Exception\InvalidVertexTypeException.
*/
namespace Gliph\Exception;
/**
* Error thrown when attempting to add a vertex of an invalid type.
*/
class InvalidVertexTypeException extends \Exception {}
\ No newline at end of file
<?php
/**
* @file
* Contains \Gliph\Exception\NonexistentVertexException.
*/
namespace Gliph\Exception;
/**
* Exception thrown when a vertex not present in a Graph is provided as a
* parameter to a method that requires the vertex to be present (e.g., removing
* the vertex, checking the edges of that vertex).
*/
class NonexistentVertexException extends \OutOfBoundsException {}
\ No newline at end of file
<?php
namespace Gliph\Exception;
/**
* OutOfRangeException for Gliph.
*/
class OutOfRangeException extends \OutOfRangeException {}
\ No newline at end of file
<?php
namespace Gliph\Exception;
/**
* RuntimeException for Gliph.
*/
class RuntimeException extends \RuntimeException {}
<?php
namespace Gliph\Exception;
/**
* An exception thrown when a method is called on a visitor that it does not
* expect in its current state.
*
* For example, this exception should be thrown by a visitor if it has a method
* that returns data produced by a full traversal algorithm, but the algorithm
* has not yet informed the visitor that it is done running.
*/
class WrongVisitorStateException extends \LogicException {}
\ No newline at end of file
<?php
namespace Gliph\Graph;
use Gliph\Exception\InvalidVertexTypeException;
use Gliph\Exception\NonexistentVertexException;
abstract class AdjacencyList implements Graph {
protected $vertices;
public function __construct() {
$this->vertices = new \SplObjectStorage();
}
/**
* {@inheritdoc}
*/
public function addVertex($vertex) {
if (!is_object($vertex)) {
throw new InvalidVertexTypeException('Vertices must be objects; non-object provided.');
}
if (!$this->hasVertex($vertex)) {
$this->vertices[$vertex] = new \SplObjectStorage();
}
return $this;
}
/**
* {@inheritdoc}
*/
public function eachAdjacent($vertex, $callback) {
if (!$this->hasVertex($vertex)) {
throw new NonexistentVertexException('Vertex is not in graph; cannot iterate over its adjacent vertices.');
}
foreach ($this->vertices[$vertex] as $adjacent_vertex) {
call_user_func($callback, $adjacent_vertex);
}
return $this;
}
/**
* {@inheritdoc}
*/
public function eachVertex($callback) {
$this->fev(function ($v, $adjacent) use ($callback) {
call_user_func($callback, $v, $adjacent);
});
return $this;
}
/**
* {@inheritdoc}
*/
public function hasVertex($vertex) {
return $this->vertices->contains($vertex);
}
protected function fev($callback) {
foreach ($this->vertices as $vertex) {
$outgoing = $this->vertices->getInfo();
$callback($vertex, $outgoing);
}
return $this;
}
}
\ No newline at end of file
<?php
namespace Gliph\Graph;
use Gliph\Algorithm\ConnectedComponent;
use Gliph\Exception\NonexistentVertexException;
use Gliph\Exception\RuntimeException;
use Gliph\Traversal\DepthFirst;
use Gliph\Visitor\DepthFirstToposortVisitor;
class DirectedAdjacencyList extends AdjacencyList implements DirectedGraph {
/**
* {@inheritdoc}
*/
public function addDirectedEdge($tail, $head) {
if (!$this->hasVertex($tail)) {
$this->addVertex(($tail));
}
if (!$this->hasVertex($head)) {
$this->addVertex($head);
}
$this->vertices[$tail]->attach($head);
}
/**
* {@inheritdoc}
*/
public function removeVertex($vertex) {
if (!$this->hasVertex($vertex)) {
throw new NonexistentVertexException('Vertex is not in the graph, it cannot be removed.', E_WARNING);
}
$this->eachVertex(function($v, $outgoing) use ($vertex) {
if ($outgoing->contains($vertex)) {
$outgoing->detach($vertex);
}
});
unset($this->vertices[$vertex]);
}
/**
* {@inheritdoc}
*/
public function removeEdge($tail, $head) {
$this->vertices[$tail]->detach($head);
}
/**
* {@inheritdoc}
*/
public function eachEdge($callback) {
$edges = array();
$this->fev(function ($from, $outgoing) use (&$edges) {
foreach ($outgoing as $to) {
$edges[] = array($from, $to);
}
});
foreach ($edges as $edge) {
call_user_func($callback, $edge);
}
}
/**
* {@inheritdoc}
*/
public function transpose() {
$graph = new self();
$this->eachEdge(function($edge) use (&$graph) {
$graph->addDirectedEdge($edge[1], $edge[0]);
});
return $graph;
}
/**
* {@inheritdoc}
*/
public function isAcyclic() {
// The DepthFirstToposortVisitor throws an exception on cycles.
try {
DepthFirst::traverse($this, new DepthFirstToposortVisitor());
return TRUE;
}
catch (RuntimeException $e) {
return FALSE;
}
}
/**
* {@inheritdoc}
*/
public function getCycles() {
$scc = ConnectedComponent::tarjan_scc($this);
return $scc->getConnectedComponents();
}
}
<?php
namespace Gliph\Graph;
/**
* Interface for directed graph datastructures.
*/
interface DirectedGraph extends Graph {
/**
* Adds a directed edge to this graph.
*
* Directed edges are also often referred to as 'arcs'.
*
* @param object $tail
* An object vertex from which the edge originates. The vertex will be
* added to the graph if it is not already present.
* @param object $head
* An object vertex to which the edge points. The vertex will be added to
* the graph if it is not already present.
*
* @return DirectedGraph
* The current graph instance.
*/
public function addDirectedEdge($tail, $head);
/**
* Returns the transpose of this graph.
*
* A transpose is identical to the current graph, except that its edges
* have had their directionality reversed.
*
* Transposed graphs are sometimes called the 'reverse' or 'converse'.
*
* @return DirectedGraph
*/
public function transpose();
/**
* Indicates whether or not this graph is acyclic.
*
* @return bool
*/
public function isAcyclic();
/**
* Returns the cycles in this graph, if any.
*
* @return array
* An array of arrays, each subarray representing a full cycle in the
* graph. If the array is empty, the graph is acyclic.
*/
public function getCycles();
}
\ No newline at end of file
<?php
namespace Gliph\Graph;
use Gliph\Exception\InvalidVertexTypeException;
use Gliph\Exception\NonexistentVertexException;
/**
* The most basic interface for graph datastructures.
*/
interface Graph {
/**
* Adds a vertex to the graph.
*
* Gliph requires that its graph vertices be objects; beyond that, it does
* not care about vertex type.
*
* @param object $vertex
* An object to use as a vertex in the graph.
*
* @return Graph
* The current graph instance.
*
* @throws InvalidVertexTypeException
* Thrown if an invalid type of data is provided as a vertex.
*/
public function addVertex($vertex);
/**
* Remove a vertex from the graph.
*
* This will also remove any edges that include the vertex.
*
* @param object $vertex
* A vertex object to remove from the graph.
*
* @return Graph
* The current graph instance.
*
* @throws NonexistentVertexException
* Thrown if the provided vertex is not present in the graph.
*/
public function removeVertex($vertex);
/**
* Removes an edge from the graph.
*
* @param $a
* The first vertex in the edge pair to remove. In a directed graph, this
* is the tail vertex.
* @param $b
* The second vertex in the edge pair to remove. In a directed graph, this
* is the head vertex.
*
* @return Graph
* The current graph instance.
*/
public function removeEdge($a, $b);
/**
* Calls the callback with each vertex adjacent to the provided vertex.
*
* The meaning of "adjacency" depends on the type of graph. In a directed
* graph, it refers to all the out-edges of the provided vertex. In an
* undirected graph, in-edges and out-edges are the same, so this method
* will iterate over both.
*
* @param object $vertex
* The vertex whose out-edges should be visited.
* @param callback $callback
* The callback to fire. For each vertex found along an out-edge, this
* callback will be called with that vertex as the sole parameter.
*
* @return Graph
* The current graph instance.
*
* @throws NonexistentVertexException
* Thrown if the vertex provided in the first parameter is not present in
* the graph.
*/
public function eachAdjacent($vertex, $callback);
/**
* Calls the provided callback for each vertex in the graph.
*
* @param $callback
* The callback is called once for each vertex in the graph. Two
* parameters are provided:
* - The vertex being inspected.
* - An SplObjectStorage containing a list of all the vertices adjacent
* to the vertex being inspected.
*
* @return Graph
* The current graph instance.
*/