diff --git a/src/Edge.php b/src/Edge.php index 47e0fd9c..10d2931b 100644 --- a/src/Edge.php +++ b/src/Edge.php @@ -106,21 +106,6 @@ public function getGraph() // @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); - } - } - /** * do NOT allow cloning of objects * diff --git a/src/Graph.php b/src/Graph.php index ccfc18f9..a8950734 100644 --- a/src/Graph.php +++ b/src/Graph.php @@ -88,67 +88,128 @@ public function createEdgeDirected(Vertex $source, Vertex $target, array $attrib } /** - * adds a new Vertex to the Graph (MUST NOT be called manually!) + * Returns a copy of this graph without the given vertex * - * @param Vertex $vertex instance of the new Vertex - * @return void - * @internal - * @see self::createVertex() instead! + * If this vertex was not found in this graph, the returned graph will be + * identical. + * + * @param Vertex $vertex + * @return self */ - public function addVertex(Vertex $vertex) + public function withoutVertex(Vertex $vertex) { - $this->vertices[] = $vertex; + return $this->withoutVertices(new Vertices(array($vertex))); } /** - * adds a new Edge to the Graph (MUST NOT be called manually!) + * Returns a copy of this graph without the given vertices * - * @param Edge $edge instance of the new Edge - * @return void - * @internal - * @see Graph::createEdgeUndirected() instead! + * If any of the given vertices can not be found in this graph, they will + * silently be ignored. If neither of the vertices can be found in this graph, + * the returned graph will be identical. + * + * @param Vertices $vertices + * @return self */ - public function addEdge(Edge $edge) + public function withoutVertices(Vertices $vertices) { - $this->edges []= $edge; + // keep copy of original vertices and edges and temporarily remove all $vertices and their adjacent edges + $originalEdges = $this->edges; + $originalVertices = $this->vertices; + foreach ($vertices as $vertex) { + if (($key = \array_search($vertex, $this->vertices, true)) !== false) { + unset($this->vertices[$key]); + foreach ($vertex->getEdges() as $edge) { + if (($key = \array_search($edge, $this->edges, true)) !== false) { + unset($this->edges[$key]); + } + } + } + } + + // no vertices matched => return graph as-is + if (\count($this->vertices) === \count($originalVertices)) { + return $this; + } + + // clone graph with vertices/edges temporarily removed, then restore + $clone = clone $this; + $this->edges= $originalEdges; + $this->vertices = $originalVertices; + + return $clone; } /** - * remove the given edge from list of connected edges (MUST NOT be called manually!) + * Returns a copy of this graph without the given edge * - * @param Edge $edge - * @return void - * @throws InvalidArgumentException if given edge does not exist (should not ever happen) - * @internal - * @see Edge::destroy() instead! + * If this edge was not found in this graph, the returned graph will be + * identical. + * + * @param Edge $edge + * @return self + */ + public function withoutEdge(Edge $edge) + { + return $this->withoutEdges(new Edges(array($edge))); + } + + /** + * Returns a copy of this graph without the given edges + * + * If any of the given edges can not be found in this graph, they will + * silently be ignored. If neither of the edges can be found in this graph, + * the returned graph will be identical. + * + * @param Edges $edges + * @return self */ - public function removeEdge(Edge $edge) + public function withoutEdges(Edges $edges) { - $key = \array_search($edge, $this->edges, true); - if ($key === false) { - throw new InvalidArgumentException('Invalid Edge does not exist in this Graph'); + // keep copy of original edges and temporarily remove all $edges + $original = $this->edges; + foreach ($edges as $edge) { + if (($key = \array_search($edge, $this->edges, true)) !== false) { + unset($this->edges[$key]); + } + } + + // no edges matched => return graph as-is + if (\count($this->edges) === \count($original)) { + return $this; } - unset($this->edges[$key]); + // clone graph with edges temporarily removed, then restore + $clone = clone $this; + $this->edges = $original; + + return $clone; } /** - * remove the given vertex from list of known vertices (MUST NOT be called manually!) + * adds a new Vertex to the Graph (MUST NOT be called manually!) * - * @param Vertex $vertex + * @param Vertex $vertex instance of the new Vertex * @return void - * @throws InvalidArgumentException if given vertex does not exist (should not ever happen) * @internal - * @see Vertex::destroy() instead! + * @see self::createVertex() instead! */ - public function removeVertex(Vertex $vertex) + public function addVertex(Vertex $vertex) { - $key = \array_search($vertex, $this->vertices, true); - if ($key === false) { - throw new InvalidArgumentException('Invalid Vertex does not exist in this Graph'); - } + $this->vertices[] = $vertex; + } - unset($this->vertices[$key]); + /** + * adds a new Edge to the Graph (MUST NOT be called manually!) + * + * @param Edge $edge instance of the new Edge + * @return void + * @internal + * @see Graph::createEdgeUndirected() instead! + */ + public function addEdge(Edge $edge) + { + $this->edges []= $edge; } /** diff --git a/src/Vertex.php b/src/Vertex.php index 9b6a7f0d..d9a3ec1c 100644 --- a/src/Vertex.php +++ b/src/Vertex.php @@ -3,7 +3,6 @@ namespace Graphp\Graph; use Graphp\Graph\Exception\BadMethodCallException; -use Graphp\Graph\Exception\InvalidArgumentException; use Graphp\Graph\Set\Edges; use Graphp\Graph\Set\EdgesAggregate; use Graphp\Graph\Set\Vertices; @@ -58,24 +57,6 @@ 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 * @@ -256,20 +237,6 @@ public function getVerticesEdgeFrom() 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 * diff --git a/src/Walk.php b/src/Walk.php index d4c4ddcb..01bd0c1c 100644 --- a/src/Walk.php +++ b/src/Walk.php @@ -200,33 +200,4 @@ public function getAlternatingSequence() 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 = \iterator_to_array($this->getGraph()->getVertices(), false); - - // check source graph contains all vertices - foreach ($this->getVertices() as $vertex) { - if (!\in_array($vertex, $vertices, true)) { - 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; - } } diff --git a/tests/EdgeTest.php b/tests/EdgeTest.php index ce918b13..f5fd845a 100644 --- a/tests/EdgeTest.php +++ b/tests/EdgeTest.php @@ -104,18 +104,6 @@ public function testLoop() $this->assertSame($this->v1, $edge->getVertexToFrom($this->v1)); } - public function testRemoveWithLoop() - { - $edge = $this->createEdgeLoop(); - - $this->assertEquals(array($this->edge, $edge), $this->graph->getEdges()->getVector()); - - $edge->destroy(); - - $this->assertEquals(array($this->edge), $this->graph->getEdges()->getVector()); - $this->assertEquals(array($this->v1, $this->v2), $this->graph->getVertices()->getVector()); - } - protected function createEntity() { return $this->createEdge(); diff --git a/tests/GraphTest.php b/tests/GraphTest.php index 619a7a8a..efc450c3 100644 --- a/tests/GraphTest.php +++ b/tests/GraphTest.php @@ -3,6 +3,8 @@ namespace Graphp\Graph\Tests; use Graphp\Graph\Graph; +use Graphp\Graph\Set\Edges; +use Graphp\Graph\Set\Vertices; class GraphTest extends EntityTest { @@ -91,7 +93,7 @@ public function testCreateMixedGraph() $this->assertEquals(array($v1), $v2->getVerticesEdgeFrom()->getVector()); } - public function testRemoveEdge() + public function testWithoutEdgeReturnsNewGraphAndDoesNotModifyOriginal() { // 1 -- 2 $graph = new Graph(); @@ -101,18 +103,14 @@ public function testRemoveEdge() $this->assertEquals(array($edge), $graph->getEdges()->getVector()); - $edge->destroy(); - //$graph->removeEdge($edge); + $new = $graph->withoutEdge($edge); - $this->assertEquals(array(), $graph->getEdges()->getVector()); - - return $graph; + $this->assertInstanceOf(get_class($graph), $new); + $this->assertEquals(array(), $new->getEdges()->getVector()); + $this->assertEquals(array($edge), $graph->getEdges()->getVector()); } - /** - * @expectedException InvalidArgumentException - */ - public function testRemoveEdgeInvalid() + public function testWithoutEdgesSameTwiceReturnsNewGraphAndDoesNotModifyOriginal() { // 1 -- 2 $graph = new Graph(); @@ -120,32 +118,135 @@ public function testRemoveEdgeInvalid() $v2 = $graph->createVertex(); $edge = $graph->createEdgeUndirected($v1, $v2); - $edge->destroy(); - $edge->destroy(); + $this->assertEquals(array($edge), $graph->getEdges()->getVector()); + + $new = $graph->withoutEdges(new Edges(array($edge, $edge))); + + $this->assertInstanceOf(get_class($graph), $new); + $this->assertEquals(array(), $new->getEdges()->getVector()); + $this->assertEquals(array($edge), $graph->getEdges()->getVector()); + } + + public function testWithoutEdgeFromOtherGraphReturnsSameGraphWithoutModification() + { + // 1 -- 2 + $graph = new Graph(); + $v1 = $graph->createVertex(); + $v2 = $graph->createVertex(); + $graph->createEdgeUndirected($v1, $v2); + + $clone = clone $graph; + $edge = $clone->getEdges()->getEdgeFirst(); + + $new = $graph->withoutEdge($edge); + + $this->assertSame($new, $graph); + $this->assertEquals(array($edge), $graph->getEdges()->getVector()); } - public function testRemoveVertex() + public function testWithoutVertexReturnsNewGraphAndDoesNotModifyOriginal() { $graph = new Graph(); $vertex = $graph->createVertex(); $this->assertEquals(array($vertex), $graph->getVertices()->getVector()); - $vertex->destroy(); + $new = $graph->withoutVertex($vertex); - $this->assertEquals(array(), $graph->getVertices()->getVector()); + $this->assertInstanceOf(get_class($graph), $new); + $this->assertEquals(array(), $new->getVertices()->getVector()); + $this->assertEquals(array($vertex), $graph->getVertices()->getVector()); } - /** - * @expectedException InvalidArgumentException - */ - public function testRemoveVertexInvalid() + public function testWithoutVerticesTwiceReturnsNewGraphAndDoesNotModifyOriginal() { $graph = new Graph(); $vertex = $graph->createVertex(); - $vertex->destroy(); - $vertex->destroy(); + $this->assertEquals(array($vertex), $graph->getVertices()->getVector()); + + $new = $graph->withoutVertices(new Vertices(array($vertex, $vertex))); + + $this->assertInstanceOf(get_class($graph), $new); + $this->assertEquals(array(), $new->getVertices()->getVector()); + $this->assertEquals(array($vertex), $graph->getVertices()->getVector()); + } + + public function testWithoutVertexFromOtherGraphReturnsSameGraphWithoutModification() + { + $graph = new Graph(); + $graph->createVertex(); + + $other = new Graph(); + $vertex = $other->createVertex(); + + $new = $graph->withoutVertex($vertex); + + $this->assertSame($new, $graph); + $this->assertEquals(array($vertex), $graph->getVertices()->getVector()); + } + + public function testWithoutVertexRemovesAttachedUndirectedEdge() + { + // 1 -- 2 + $graph = new Graph(); + $v1 = $graph->createVertex(); + $v2 = $graph->createVertex(); + $graph->createEdgeUndirected($v1, $v2); + + $new = $graph->withoutVertex($v1); + + $this->assertCount(1, $new->getVertices()); + $this->assertCount(0, $new->getEdges()); + } + + public function testWithoutVertexWithUndirectedLoopReturnsEmptyGraph() + { + // 1 -\ + // | | + // \--/ + $graph = new Graph(); + $v1 = $graph->createVertex(); + $graph->createEdgeUndirected($v1, $v1); + + $new = $graph->withoutVertex($v1); + + $this->assertCount(0, $new->getVertices()); + $this->assertCount(0, $new->getEdges()); + } + + public function testWithoutVertexWithUndirectedLoopReturnsRemainingGraph() + { + // 1 -- 2 -\ + // ^ | | + // | \--/ + // 3 + $graph = new Graph(); + $v1 = $graph->createVertex(); + $v2 = $graph->createVertex(); + $v3 = $graph->createVertex(); + $graph->createEdgeDirected($v3, $v1); + $graph->createEdgeUndirected($v2, $v2); + + $new = $graph->withoutVertex($v2); + + $this->assertCount(2, $new->getVertices()); + $this->assertCount(1, $new->getEdges()); + } + + public function testWithoutVertexWithDirectedLoopReturnsEmptyGraph() + { + // 1 -\ + // ^ | + // \--/ + $graph = new Graph(); + $v1 = $graph->createVertex(); + $graph->createEdgeDirected($v1, $v1); + + $new = $graph->withoutVertex($v1); + + $this->assertCount(0, $new->getVertices()); + $this->assertCount(0, $new->getEdges()); } public function testGraphCloneEmptyGraph() diff --git a/tests/VertexTest.php b/tests/VertexTest.php index fbe720cf..e0caabd6 100644 --- a/tests/VertexTest.php +++ b/tests/VertexTest.php @@ -123,44 +123,6 @@ public function testCreateEdgeDirectedOtherGraphFails() $this->graph->createEdgeDirected($this->vertex, $graphOther->createVertex()); } - /** - * @expectedException InvalidArgumentException - */ - public function testRemoveInvalidEdge() - { - // 2 -- 3 - $v2 = $this->graph->createVertex(); - $v3 = $this->graph->createVertex(); - $edge = $this->graph->createEdgeUndirected($v2, $v3); - - $this->vertex->removeEdge($edge); - } - - public function testRemoveWithEdgeLoopUndirected() - { - // 1 -- 1 - $this->graph->createEdgeUndirected($this->vertex, $this->vertex); - - $this->assertEquals(array($this->vertex), $this->graph->getVertices()->getVector()); - - $this->vertex->destroy(); - - $this->assertEquals(array(), $this->graph->getVertices()->getVector()); - $this->assertEquals(array(), $this->graph->getEdges()->getVector()); - } - - public function testRemoveWithEdgeLoopDirected() - { - // 1 --> 1 - $this->graph->createEdgeDirected($this->vertex, $this->vertex); - - $this->assertEquals(array($this->vertex), $this->graph->getVertices()->getVector()); - - $this->vertex->destroy(); - - $this->assertEquals(array(), $this->graph->getVertices()->getVector()); - $this->assertEquals(array(), $this->graph->getEdges()->getVector()); - } protected function createEntity() { diff --git a/tests/WalkTest.php b/tests/WalkTest.php index c702444c..673f2469 100644 --- a/tests/WalkTest.php +++ b/tests/WalkTest.php @@ -30,28 +30,16 @@ public function testWalkPath() $walk = Walk::factoryFromEdges(new Edges(array($e1, $e2)), $v1); + $this->assertSame($graph, $walk->getGraph()); $this->assertEquals(3, count($walk->getVertices())); $this->assertEquals(2, count($walk->getEdges())); $this->assertSame($v1, $walk->getVertices()->getVertexFirst()); $this->assertSame($v3, $walk->getVertices()->getVertexLast()); $this->assertSame(array($v1, $e1, $v2, $e2, $v3), $walk->getAlternatingSequence()); - $this->assertTrue($walk->isValid()); return $walk; } - /** - * @param Walk $walk - * @depends testWalkPath - */ - public function testWalkPathInvalidateByDestroyingVertex(Walk $walk) - { - // delete v3 - $walk->getVertices()->getVertexLast()->destroy(); - - $this->assertFalse($walk->isValid()); - } - public function testWalkWithinGraph() { // 1 -- 2 -- 3 @@ -65,12 +53,12 @@ public function testWalkWithinGraph() // construct partial walk "1 -- 2" $walk = Walk::factoryFromEdges(new Edges(array($e1)), $v1); + $this->assertSame($graph, $walk->getGraph()); $this->assertEquals(2, count($walk->getVertices())); $this->assertEquals(1, count($walk->getEdges())); $this->assertSame($v1, $walk->getVertices()->getVertexFirst()); $this->assertSame($v2, $walk->getVertices()->getVertexLast()); $this->assertSame(array($v1, $e1, $v2), $walk->getAlternatingSequence()); - $this->assertTrue($walk->isValid()); // construct same partial walk "1 -- 2" $walkVertices = Walk::factoryFromVertices(new Vertices(array($v1, $v2))); @@ -94,26 +82,10 @@ public function testWalkLoop() $this->assertEquals(1, count($walk->getEdges())); $this->assertSame($v1, $walk->getVertices()->getVertexFirst()); $this->assertSame($v1, $walk->getVertices()->getVertexLast()); - $this->assertTrue($walk->isValid()); return $walk; } - /** - * @param Walk $walk - * @depends testWalkLoop - */ - public function testWalkInvalidByDestroyingEdge(Walk $walk) - { - // destroy first edge found - foreach ($walk->getEdges() as $edge) { - $edge->destroy(); - break; - } - - $this->assertFalse($walk->isValid()); - } - public function testWalkLoopCycle() { // 1 -- 1 @@ -127,7 +99,6 @@ public function testWalkLoopCycle() $this->assertEquals(1, count($walk->getEdges())); $this->assertSame($v1, $walk->getVertices()->getVertexFirst()); $this->assertSame($v1, $walk->getVertices()->getVertexLast()); - $this->assertTrue($walk->isValid()); } /** @@ -175,7 +146,6 @@ public function testFactoryCycleFromEdgesWithLoopCycle() $this->assertCount(1, $cycle->getEdges()); $this->assertSame($v1, $cycle->getVertices()->getVertexFirst()); $this->assertSame($v1, $cycle->getVertices()->getVertexLast()); - $this->assertTrue($cycle->isValid()); } public function testFactoryCycleFromVerticesWithLoopCycle() @@ -193,7 +163,6 @@ public function testFactoryCycleFromVerticesWithLoopCycle() $this->assertCount(1, $cycle->getEdges()); $this->assertSame($v1, $cycle->getVertices()->getVertexFirst()); $this->assertSame($v1, $cycle->getVertices()->getVertexLast()); - $this->assertTrue($cycle->isValid()); }