org.jgrapht.graph.AsSubgraph Maven / Gradle / Ivy
/*
* (C) Copyright 2003-2021, by Barak Naveh and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.graph;
import org.jgrapht.*;
import org.jgrapht.event.*;
import java.io.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
/**
* A subgraph is a graph that has a subset of vertices and a subset of edges with respect to some
* base graph. More formally, a subgraph G(V,E) that is based on a base graph Gb(Vb,Eb) satisfies
* the following subgraph property: V is a subset of Vb and E is a subset of Eb. Other
* than this property, a subgraph is a graph with any respect and fully complies with the
* Graph
interface.
*
*
* If the base graph is a {@link org.jgrapht.ListenableGraph}, the subgraph listens on the base
* graph and guarantees the subgraph property. If an edge or a vertex is removed from the base
* graph, it is automatically removed from the subgraph. Subgraph listeners are informed on such
* removal only if it results in a cascaded removal from the subgraph. If the subgraph has been
* created as an induced subgraph it also keeps track of edges being added to its vertices. If
* vertices are added to the base graph, the subgraph remains unaffected.
*
*
*
* If the base graph is not a ListenableGraph, then the subgraph property cannot be
* guaranteed. If edges or vertices are removed from the base graph, they are not removed
* from the subgraph.
*
*
*
* Modifications to Subgraph are allowed as long as the subgraph property is maintained. Addition of
* vertices or edges are allowed as long as they also exist in the base graph. Removal of vertices
* or edges is always allowed. The base graph is never affected by any modification made to
* the subgraph.
*
*
*
* A subgraph may provide a "live-window" on a base graph, so that changes made to its vertices or
* edges are immediately reflected in the base graph, and vice versa. For that to happen, vertices
* and edges added to the subgraph must be identical (that is, reference-equal and not only
* value-equal) to their respective ones in the base graph. Previous versions of this class enforced
* such identity, at a severe performance cost. Currently it is no longer enforced. If you want to
* achieve a "live-window" functionality, your safest tactics would be to NOT override the
* equals()
methods of your vertices and edges. If you use a class that has already
* overridden the equals()
method, such as String
, then you can use a
* wrapper around it, or else use it directly but exercise a great care to avoid having
* different-but-equal instances in the subgraph and the base graph.
*
*
*
* This graph implementation guarantees deterministic vertex and edge set ordering (via
* {@link LinkedHashSet}).
*
*
*
* Note that this implementation tries to maintain a "live-window" on the base graph, which has
* implications in the performance of the various operations. For example iterating over the
* adjacent edges of a vertex takes time proportional to the number of adjacent edges of the vertex
* in the base graph even if the subgraph contains only a small subset of those edges. Therefore,
* the user must be aware that using this implementation for certain algorithms might come with
* computational overhead. For certain algorithms it is better to maintain a subgraph by hand
* instead of using this implementation as a black box.
*
* @param the vertex type
* @param the edge type
*
* @author Barak Naveh
* @see Graph
* @see Set
*/
public class AsSubgraph
extends
AbstractGraph
implements
Serializable
{
private static final long serialVersionUID = -1471811754881775298L;
private static final String NO_SUCH_EDGE_IN_BASE = "no such edge in base graph";
private static final String NO_SUCH_VERTEX_IN_BASE = "no such vertex in base graph";
private static final String CANNOT_CREATE_NEW_VERTICES_FROM_SUBGRAPH =
"Cannot create new vertices from subgraph";
protected final Set edgeSet = new LinkedHashSet<>();
protected final Set vertexSet = new LinkedHashSet<>();
protected final Graph base;
protected final GraphType baseType;
protected final boolean isInduced;
private transient Set unmodifiableEdgeSet = null;
private transient Set unmodifiableVertexSet = null;
/**
* Creates a new subgraph.
*
* @param base the base (backing) graph on which the subgraph will be based.
* @param vertexSubset vertices to include in the subgraph. If null
then all
* vertices are included.
* @param edgeSubset edges to in include in the subgraph. If null
then all the
* edges whose vertices found in the graph are included.
*/
public AsSubgraph(Graph base, Set extends V> vertexSubset, Set extends E> edgeSubset)
{
super();
this.base = GraphTests.requireDirectedOrUndirected(base);
this.baseType = base.getType();
this.isInduced = edgeSubset == null;
if (base instanceof ListenableGraph, ?>) {
((ListenableGraph) base).addGraphListener(new BaseGraphListener());
}
initialize(vertexSubset, edgeSubset);
}
/**
* Creates a new induced subgraph. The subgraph will keep track of edges being added to its
* vertex subset as well as deletion of edges and vertices. If base it not listenable, this is
* identical to the call Subgraph(base, vertexSubset, null).
*
* @param base the base (backing) graph on which the subgraph will be based.
* @param vertexSubset vertices to include in the subgraph. If null
then all
* vertices are included.
*/
public AsSubgraph(Graph base, Set extends V> vertexSubset)
{
this(base, vertexSubset, null);
}
/**
* Creates a new induced Subgraph with all vertices included. The subgraph will keep track of
* edges being added to its vertex subset as well as deletion of edges and vertices. If base is
* not listenable, this is identical to the call Subgraph(base, null, null).
*
* @param base the base (backing) graph on which the subgraph will be based.
*/
public AsSubgraph(Graph base)
{
this(base, null, null);
}
/**
* {@inheritDoc}
*/
@Override
public Set getAllEdges(V sourceVertex, V targetVertex)
{
if (containsVertex(sourceVertex) && containsVertex(targetVertex)) {
return base
.getAllEdges(sourceVertex, targetVertex).stream().filter(edgeSet::contains)
.collect(Collectors.toCollection(LinkedHashSet::new));
} else {
return null;
}
}
/**
* {@inheritDoc}
*/
@Override
public E getEdge(V sourceVertex, V targetVertex)
{
Set edges = getAllEdges(sourceVertex, targetVertex);
if (edges == null) {
return null;
} else {
return edges.stream().findAny().orElse(null);
}
}
/**
* {@inheritDoc}
*/
@Override
public Supplier getVertexSupplier()
{
return base.getVertexSupplier();
}
/**
* {@inheritDoc}
*/
@Override
public Supplier getEdgeSupplier()
{
return base.getEdgeSupplier();
}
/**
* Add an edge to the subgraph. The end-points must exist in the subgraph and the edge must
* exist in the base graph. In case multiple such edges exist in the base graph, one that is not
* already in the subgraph is chosen arbitrarily and added to the subgraph. In case all such
* edges already exist in the subgraph, the method returns null.
*
* @param sourceVertex the source vertex
* @param targetVertex the source vertex
* @return the added edge or null if all such edges from the base graph already belong in the
* subgraph
* @throws IllegalArgumentException if the source or target vertex does not belong to the
* subgraph
* @throws IllegalArgumentException if the base graph does not contain any edge between the two
* end-points
*/
@Override
public E addEdge(V sourceVertex, V targetVertex)
{
assertVertexExist(sourceVertex);
assertVertexExist(targetVertex);
if (!base.containsEdge(sourceVertex, targetVertex)) {
throw new IllegalArgumentException(NO_SUCH_EDGE_IN_BASE);
}
Set edges = base.getAllEdges(sourceVertex, targetVertex);
for (E e : edges) {
if (!containsEdge(e)) {
edgeSet.add(e);
return e;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean addEdge(V sourceVertex, V targetVertex, E e)
{
if (e == null) {
throw new NullPointerException();
}
if (!base.containsEdge(e)) {
throw new IllegalArgumentException(NO_SUCH_EDGE_IN_BASE);
}
assertVertexExist(sourceVertex);
assertVertexExist(targetVertex);
assert (base.getEdgeSource(e) == sourceVertex);
assert (base.getEdgeTarget(e) == targetVertex);
return edgeSet.add(e);
}
@Override
public V addVertex()
{
throw new UnsupportedOperationException(CANNOT_CREATE_NEW_VERTICES_FROM_SUBGRAPH);
}
/**
* Adds the specified vertex to this subgraph.
*
* @param v the vertex to be added.
*
* @return true
if the vertex was added, otherwise
* false
.
*
* @throws NullPointerException if v is null
* @throws IllegalArgumentException if the base graph does not contain the vertex
*
* @see AsSubgraph
* @see Graph#addVertex(Object)
*/
@Override
public boolean addVertex(V v)
{
if (v == null) {
throw new NullPointerException();
}
if (!base.containsVertex(v)) {
throw new IllegalArgumentException(NO_SUCH_VERTEX_IN_BASE);
}
return vertexSet.add(v);
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsEdge(E e)
{
return edgeSet.contains(e);
}
/**
* {@inheritDoc}
*/
@Override
public boolean containsVertex(V v)
{
return vertexSet.contains(v);
}
/**
* {@inheritDoc}
*/
@Override
public Set edgeSet()
{
if (unmodifiableEdgeSet == null) {
unmodifiableEdgeSet = Collections.unmodifiableSet(edgeSet);
}
return unmodifiableEdgeSet;
}
/**
* {@inheritDoc}
*/
@Override
public Set edgesOf(V vertex)
{
assertVertexExist(vertex);
return base
.edgesOf(vertex).stream().filter(edgeSet::contains)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* {@inheritDoc}
*
*
* By default this method returns the sum of in-degree and out-degree. The exact value returned
* depends on the types of the underlying graph.
*/
@Override
public int degreeOf(V vertex)
{
assertVertexExist(vertex);
if (baseType.isUndirected()) {
int degree = 0;
Iterator it = base.edgesOf(vertex).stream().filter(edgeSet::contains).iterator();
while (it.hasNext()) {
E e = it.next();
degree++;
if (getEdgeSource(e).equals(getEdgeTarget(e))) {
degree++;
}
}
return degree;
} else {
return inDegreeOf(vertex) + outDegreeOf(vertex);
}
}
/**
* {@inheritDoc}
*/
@Override
public Set incomingEdgesOf(V vertex)
{
assertVertexExist(vertex);
return base
.incomingEdgesOf(vertex).stream().filter(edgeSet::contains)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* {@inheritDoc}
*/
@Override
public int inDegreeOf(V vertex)
{
if (baseType.isUndirected()) {
return degreeOf(vertex);
} else {
return incomingEdgesOf(vertex).size();
}
}
/**
* {@inheritDoc}
*/
@Override
public Set outgoingEdgesOf(V vertex)
{
assertVertexExist(vertex);
return base
.outgoingEdgesOf(vertex).stream().filter(edgeSet::contains)
.collect(Collectors.toCollection(LinkedHashSet::new));
}
/**
* {@inheritDoc}
*/
@Override
public int outDegreeOf(V vertex)
{
if (baseType.isUndirected()) {
return degreeOf(vertex);
} else {
return outgoingEdgesOf(vertex).size();
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean removeEdge(E e)
{
return edgeSet.remove(e);
}
/**
* {@inheritDoc}
*/
@Override
public E removeEdge(V sourceVertex, V targetVertex)
{
E e = getEdge(sourceVertex, targetVertex);
return edgeSet.remove(e) ? e : null;
}
/**
* {@inheritDoc}
*/
@Override
public boolean removeVertex(V v)
{
// If the base graph does NOT contain v it means we are here in
// response to removal of v from the base. In such case we don't need
// to remove all the edges of v as they were already removed.
if (containsVertex(v) && base.containsVertex(v)) {
removeAllEdges(edgesOf(v));
}
return vertexSet.remove(v);
}
/**
* {@inheritDoc}
*/
@Override
public Set vertexSet()
{
if (unmodifiableVertexSet == null) {
unmodifiableVertexSet = Collections.unmodifiableSet(vertexSet);
}
return unmodifiableVertexSet;
}
/**
* {@inheritDoc}
*/
@Override
public V getEdgeSource(E e)
{
return base.getEdgeSource(e);
}
/**
* {@inheritDoc}
*/
@Override
public V getEdgeTarget(E e)
{
return base.getEdgeTarget(e);
}
/**
* {@inheritDoc}
*/
@Override
public GraphType getType()
{
return base.getType();
}
/**
* {@inheritDoc}
*/
@Override
public double getEdgeWeight(E e)
{
return base.getEdgeWeight(e);
}
/**
* {@inheritDoc}
*/
@Override
public void setEdgeWeight(E e, double weight)
{
base.setEdgeWeight(e, weight);
}
private void initialize(Set extends V> vertexFilter, Set extends E> edgeFilter)
{
if (vertexFilter == null && edgeFilter == null) {
vertexSet.addAll(base.vertexSet());
edgeSet.addAll(base.edgeSet());
return;
}
// add vertices
if (vertexFilter == null) {
vertexSet.addAll(base.vertexSet());
} else {
if (vertexFilter.size() > base.vertexSet().size()) {
base.vertexSet().stream().filter(vertexFilter::contains).forEach(vertexSet::add);
} else {
vertexFilter
.stream().filter(v -> v != null && base.containsVertex(v))
.forEach(vertexSet::add);
}
}
// add edges
if (edgeFilter == null) {
if (vertexSet.size() > base.edgeSet().size()) {
base
.edgeSet().stream()
.filter(
e -> vertexSet.contains(base.getEdgeSource(e))
&& vertexSet.contains(base.getEdgeTarget(e)))
.forEach(edgeSet::add);
} else {
vertexSet.forEach(v ->
base.edgesOf(v).stream()
.filter(
e -> vertexSet.contains(base.getEdgeSource(e))
&& vertexSet.contains(base.getEdgeTarget(e)))
.forEach(edgeSet::add)
);
}
} else {
if (edgeFilter.size() > base.edgeSet().size()) {
base
.edgeSet().stream()
.filter(
e -> edgeFilter.contains(e) && vertexSet.contains(base.getEdgeSource(e))
&& vertexSet.contains(base.getEdgeTarget(e)))
.forEach(edgeSet::add);
} else {
edgeFilter
.stream()
.filter(
e -> e != null && base.containsEdge(e)
&& vertexSet.contains(base.getEdgeSource(e))
&& vertexSet.contains(base.getEdgeTarget(e)))
.forEach(edgeSet::add);
}
}
}
/**
* An internal listener on the base graph.
*
* @author Barak Naveh
*/
private class BaseGraphListener
implements
GraphListener,
Serializable
{
private static final long serialVersionUID = 4343535244243546391L;
/**
* {@inheritDoc}
*/
@Override
public void edgeAdded(GraphEdgeChangeEvent e)
{
if (isInduced) {
E edge = e.getEdge();
V source = e.getEdgeSource();
V target = e.getEdgeTarget();
if (containsVertex(source) && containsVertex(target)) {
addEdge(source, target, edge);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void edgeRemoved(GraphEdgeChangeEvent e)
{
E edge = e.getEdge();
removeEdge(edge);
}
/**
* {@inheritDoc}
*/
@Override
public void vertexAdded(GraphVertexChangeEvent e)
{
// we don't care
}
/**
* {@inheritDoc}
*/
@Override
public void vertexRemoved(GraphVertexChangeEvent e)
{
V vertex = e.getVertex();
removeVertex(vertex);
}
}
}