org.jgrapht.alg.util.NeighborCache Maven / Gradle / Ivy
/*
* (C) Copyright 2017-2023, by Szabolcs Besenyei 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.alg.util;
import org.jgrapht.*;
import org.jgrapht.event.*;
import org.jgrapht.util.*;
import java.util.*;
import java.util.function.*;
/**
* Maintains a cache of each vertex's neighbors. While lists of neighbors can be obtained from
* {@link Graphs}, they are re-calculated at each invocation by walking a vertex's incident edges,
* which becomes inordinately expensive when performed often.
*
*
* The cache also keeps track of successors and predecessors for each vertex. This means that the
* result of the union of calling predecessorsOf(v) and successorsOf(v) is equal to the result of
* calling neighborsOf(v) for a given vertex v.
*
* @param the vertex type
* @param the edge type
*
* @author Szabolcs Besenyei
*/
public class NeighborCache
implements GraphListener
{
private Map> successors = new HashMap<>();
private Map> predecessors = new HashMap<>();
private Map> neighbors = new HashMap<>();
private Graph graph;
/**
* Constructor
*
* @param graph the input graph
* @throws NullPointerException if the input graph is null
*/
public NeighborCache(Graph graph)
{
this.graph = Objects.requireNonNull(graph);
}
/**
* Returns the unique predecessors of the given vertex if it exists in the cache, otherwise it
* is initialized.
*
* @param v the given vertex
* @return the unique predecessors of the given vertex
*/
public Set predecessorsOf(V v)
{
return fetch(v, predecessors, k -> new Neighbors<>(Graphs.predecessorListOf(graph, v)));
}
/**
* Returns the unique successors of the given vertex if it exists in the cache, otherwise it is
* initialized.
*
* @param v the given vertex
* @return the unique successors of the given vertex
*/
public Set successorsOf(V v)
{
return fetch(v, successors, k -> new Neighbors<>(Graphs.successorListOf(graph, v)));
}
/**
* Returns the unique neighbors of the given vertex if it exists in the cache, otherwise it is
* initialized.
*
* @param v the given vertex
* @return the unique neighbors of the given vertex
*/
public Set neighborsOf(V v)
{
return fetch(v, neighbors, k -> new Neighbors<>(Graphs.neighborListOf(graph, v)));
}
/**
* Returns a list of vertices which are adjacent to a specified vertex. If the graph is a
* multigraph, vertices may appear more than once in the returned list. Because a list of
* neighbors can not be efficiently maintained, it is reconstructed on every invocation, by
* duplicating entries in the neighbor set. It is thus more efficient to use
* {@link #neighborsOf} unless duplicate neighbors are important.
*
* @param v the vertex whose neighbors are desired
*
* @return all neighbors of the specified vertex
*/
public List neighborListOf(V v)
{
Neighbors nbrs = neighbors.get(v);
if (nbrs == null) {
nbrs = new Neighbors<>(Graphs.neighborListOf(graph, v));
neighbors.put(v, nbrs);
}
return nbrs.getNeighborList();
}
private Set fetch(V vertex, Map> map, Function> func)
{
return map.computeIfAbsent(vertex, func).getNeighbors();
}
@Override
public void edgeAdded(GraphEdgeChangeEvent e)
{
assert e
.getSource() == this.graph : "This NeighborCache is added as a listener to a graph other than the one specified during the construction of this NeighborCache!";
V source = e.getEdgeSource();
V target = e.getEdgeTarget();
if (successors.containsKey(source)) {
successors.get(source).addNeighbor(target);
}
if (predecessors.containsKey(target)) {
predecessors.get(target).addNeighbor(source);
}
if (neighbors.containsKey(source)) {
neighbors.get(source).addNeighbor(target);
}
if (neighbors.containsKey(target)) {
neighbors.get(target).addNeighbor(source);
}
}
@Override
public void edgeRemoved(GraphEdgeChangeEvent e)
{
assert e
.getSource() == this.graph : "This NeighborCache is added as a listener to a graph other than the one specified during the construction of this NeighborCache!";
V source = e.getEdgeSource();
V target = e.getEdgeTarget();
if (successors.containsKey(source)) {
successors.get(source).removeNeighbor(target);
}
if (predecessors.containsKey(target)) {
predecessors.get(target).removeNeighbor(source);
}
if (neighbors.containsKey(source)) {
neighbors.get(source).removeNeighbor(target);
}
if (neighbors.containsKey(target)) {
neighbors.get(target).removeNeighbor(source);
}
}
@Override
public void vertexAdded(GraphVertexChangeEvent e)
{
// Nothing to cache until there are edges
}
@Override
public void vertexRemoved(GraphVertexChangeEvent e)
{
assert e
.getSource() == this.graph : "This NeighborCache is added as a listener to a graph other than the one specified during the construction of this NeighborCache!";
successors.remove(e.getVertex());
predecessors.remove(e.getVertex());
neighbors.remove(e.getVertex());
}
/**
* Stores cached neighbors for a single vertex. Includes support for live neighbor sets and
* duplicate neighbors.
*/
static class Neighbors
{
private Map neighborCounts = new LinkedHashMap<>();
// TODO could eventually make neighborSet modifiable, resulting
// in edge removals from the graph
private Set neighborSet = Collections.unmodifiableSet(neighborCounts.keySet());
public Neighbors(Collection neighbors)
{
// add all current neighbors
for (V neighbor : neighbors) {
addNeighbor(neighbor);
}
}
public void addNeighbor(V v)
{
ModifiableInteger count = neighborCounts.get(v);
if (count == null) {
count = new ModifiableInteger(1);
neighborCounts.put(v, count);
} else {
count.increment();
}
}
public void removeNeighbor(V v)
{
ModifiableInteger count = neighborCounts.get(v);
if (count == null) {
throw new IllegalArgumentException(
"Attempting to remove a neighbor that wasn't present");
}
count.decrement();
if (count.getValue() == 0) {
neighborCounts.remove(v);
}
}
public Set getNeighbors()
{
return neighborSet;
}
public List getNeighborList()
{
List neighbors = new ArrayList<>();
for (Map.Entry entry : neighborCounts.entrySet()) {
V v = entry.getKey();
int count = entry.getValue().intValue();
for (int i = 0; i < count; i++) {
neighbors.add(v);
}
}
return neighbors;
}
@Override
public String toString()
{
return neighborSet.toString();
}
}
}