aboutsummaryrefslogtreecommitdiff
path: root/guava/src/com/google/common/graph/DirectedGraphConnections.java
diff options
context:
space:
mode:
Diffstat (limited to 'guava/src/com/google/common/graph/DirectedGraphConnections.java')
-rw-r--r--guava/src/com/google/common/graph/DirectedGraphConnections.java402
1 files changed, 355 insertions, 47 deletions
diff --git a/guava/src/com/google/common/graph/DirectedGraphConnections.java b/guava/src/com/google/common/graph/DirectedGraphConnections.java
index 0c86a75aa..3838bcfe8 100644
--- a/guava/src/com/google/common/graph/DirectedGraphConnections.java
+++ b/guava/src/com/google/common/graph/DirectedGraphConnections.java
@@ -16,6 +16,7 @@
package com.google.common.graph;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.graph.GraphConstants.INNER_CAPACITY;
@@ -23,22 +24,29 @@ import static com.google.common.graph.GraphConstants.INNER_LOAD_FACTOR;
import static com.google.common.graph.Graphs.checkNonNegative;
import static com.google.common.graph.Graphs.checkPositive;
+import com.google.common.base.Function;
import com.google.common.collect.AbstractIterator;
-import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterators;
import com.google.common.collect.UnmodifiableIterator;
import java.util.AbstractSet;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* An implementation of {@link GraphConnections} for directed graphs.
*
* @author James Sexton
+ * @author Jens Nyman
* @param <N> Node parameter type
* @param <V> Value parameter type
*/
@@ -55,18 +63,88 @@ final class DirectedGraphConnections<N, V> implements GraphConnections<N, V> {
}
}
+ /**
+ * A value class representing single connection between the origin node and another node.
+ *
+ * <p>There can be two types of connections (predecessor and successor), which is represented by
+ * the two implementations.
+ */
+ private abstract static class NodeConnection<N> {
+ final N node;
+
+ NodeConnection(N node) {
+ this.node = checkNotNull(node);
+ }
+
+ static final class Pred<N> extends NodeConnection<N> {
+ Pred(N node) {
+ super(node);
+ }
+
+ @Override
+ public boolean equals(Object that) {
+ if (that instanceof Pred) {
+ return this.node.equals(((Pred<?>) that).node);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ // Adding the class hashCode to avoid a clash with Succ instances.
+ return Pred.class.hashCode() + node.hashCode();
+ }
+ }
+
+ static final class Succ<N> extends NodeConnection<N> {
+ Succ(N node) {
+ super(node);
+ }
+
+ @Override
+ public boolean equals(Object that) {
+ if (that instanceof Succ) {
+ return this.node.equals(((Succ<?>) that).node);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ // Adding the class hashCode to avoid a clash with Pred instances.
+ return Succ.class.hashCode() + node.hashCode();
+ }
+ }
+ }
+
private static final Object PRED = new Object();
// Every value in this map must either be an instance of PredAndSucc with a successorValue of
// type V, PRED (representing predecessor), or an instance of type V (representing successor).
private final Map<N, Object> adjacentNodeValues;
+ /**
+ * All node connections in this graph, in edge insertion order.
+ *
+ * <p>Note: This field and {@link #adjacentNodeValues} cannot be combined into a single
+ * LinkedHashMap because one target node may be mapped to both a predecessor and a successor. A
+ * LinkedHashMap combines two such edges into a single node-value pair, even though the edges may
+ * not have been inserted consecutively.
+ */
+ @Nullable private final List<NodeConnection<N>> orderedNodeConnections;
+
private int predecessorCount;
private int successorCount;
private DirectedGraphConnections(
- Map<N, Object> adjacentNodeValues, int predecessorCount, int successorCount) {
+ Map<N, Object> adjacentNodeValues,
+ @Nullable List<NodeConnection<N>> orderedNodeConnections,
+ int predecessorCount,
+ int successorCount) {
this.adjacentNodeValues = checkNotNull(adjacentNodeValues);
+ this.orderedNodeConnections = orderedNodeConnections;
this.predecessorCount = checkNonNegative(predecessorCount);
this.successorCount = checkNonNegative(successorCount);
checkState(
@@ -74,30 +152,120 @@ final class DirectedGraphConnections<N, V> implements GraphConnections<N, V> {
&& successorCount <= adjacentNodeValues.size());
}
- static <N, V> DirectedGraphConnections<N, V> of() {
+ static <N, V> DirectedGraphConnections<N, V> of(ElementOrder<N> incidentEdgeOrder) {
// We store predecessors and successors in the same map, so double the initial capacity.
int initialCapacity = INNER_CAPACITY * 2;
- return new DirectedGraphConnections<>(
- new HashMap<N, Object>(initialCapacity, INNER_LOAD_FACTOR), 0, 0);
+
+ List<NodeConnection<N>> orderedNodeConnections;
+ switch (incidentEdgeOrder.type()) {
+ case UNORDERED:
+ orderedNodeConnections = null;
+ break;
+ case STABLE:
+ orderedNodeConnections = new ArrayList<NodeConnection<N>>();
+ break;
+ default:
+ throw new AssertionError(incidentEdgeOrder.type());
+ }
+
+ return new DirectedGraphConnections<N, V>(
+ /* adjacentNodeValues = */ new HashMap<N, Object>(initialCapacity, INNER_LOAD_FACTOR),
+ orderedNodeConnections,
+ /* predecessorCount = */ 0,
+ /* successorCount = */ 0);
}
static <N, V> DirectedGraphConnections<N, V> ofImmutable(
- Set<N> predecessors, Map<N, V> successorValues) {
+ N thisNode, Iterable<EndpointPair<N>> incidentEdges, Function<N, V> successorNodeToValueFn) {
+ checkNotNull(thisNode);
+ checkNotNull(successorNodeToValueFn);
+
Map<N, Object> adjacentNodeValues = new HashMap<>();
- adjacentNodeValues.putAll(successorValues);
- for (N predecessor : predecessors) {
- Object value = adjacentNodeValues.put(predecessor, PRED);
- if (value != null) {
- adjacentNodeValues.put(predecessor, new PredAndSucc(value));
+ ImmutableList.Builder<NodeConnection<N>> orderedNodeConnectionsBuilder =
+ ImmutableList.builder();
+ int predecessorCount = 0;
+ int successorCount = 0;
+
+ for (EndpointPair<N> incidentEdge : incidentEdges) {
+ if (incidentEdge.nodeU().equals(thisNode) && incidentEdge.nodeV().equals(thisNode)) {
+ // incidentEdge is a self-loop
+
+ adjacentNodeValues.put(thisNode, new PredAndSucc(successorNodeToValueFn.apply(thisNode)));
+
+ orderedNodeConnectionsBuilder.add(new NodeConnection.Pred<>(thisNode));
+ orderedNodeConnectionsBuilder.add(new NodeConnection.Succ<>(thisNode));
+ predecessorCount++;
+ successorCount++;
+ } else if (incidentEdge.nodeV().equals(thisNode)) { // incidentEdge is an inEdge
+ N predecessor = incidentEdge.nodeU();
+
+ Object existingValue = adjacentNodeValues.put(predecessor, PRED);
+ if (existingValue != null) {
+ adjacentNodeValues.put(predecessor, new PredAndSucc(existingValue));
+ }
+
+ orderedNodeConnectionsBuilder.add(new NodeConnection.Pred<>(predecessor));
+ predecessorCount++;
+ } else { // incidentEdge is an outEdge
+ checkArgument(incidentEdge.nodeU().equals(thisNode));
+
+ N successor = incidentEdge.nodeV();
+ V value = successorNodeToValueFn.apply(successor);
+
+ Object existingValue = adjacentNodeValues.put(successor, value);
+ if (existingValue != null) {
+ checkArgument(existingValue == PRED);
+ adjacentNodeValues.put(successor, new PredAndSucc(value));
+ }
+
+ orderedNodeConnectionsBuilder.add(new NodeConnection.Succ<>(successor));
+ successorCount++;
}
}
+
return new DirectedGraphConnections<>(
- ImmutableMap.copyOf(adjacentNodeValues), predecessors.size(), successorValues.size());
+ adjacentNodeValues,
+ orderedNodeConnectionsBuilder.build(),
+ predecessorCount,
+ successorCount);
}
@Override
public Set<N> adjacentNodes() {
- return Collections.unmodifiableSet(adjacentNodeValues.keySet());
+ if (orderedNodeConnections == null) {
+ return Collections.unmodifiableSet(adjacentNodeValues.keySet());
+ } else {
+ return new AbstractSet<N>() {
+ @Override
+ public UnmodifiableIterator<N> iterator() {
+ final Iterator<NodeConnection<N>> nodeConnections = orderedNodeConnections.iterator();
+ final Set<N> seenNodes = new HashSet<>();
+ return new AbstractIterator<N>() {
+ @Override
+ protected N computeNext() {
+ while (nodeConnections.hasNext()) {
+ NodeConnection<N> nodeConnection = nodeConnections.next();
+ boolean added = seenNodes.add(nodeConnection.node);
+ if (added) {
+ return nodeConnection.node;
+ }
+ }
+ return endOfData();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return adjacentNodeValues.size();
+ }
+
+ @Override
+ public boolean contains(@Nullable Object obj) {
+ return adjacentNodeValues.containsKey(obj);
+ }
+ };
+ }
}
@Override
@@ -105,19 +273,35 @@ final class DirectedGraphConnections<N, V> implements GraphConnections<N, V> {
return new AbstractSet<N>() {
@Override
public UnmodifiableIterator<N> iterator() {
- final Iterator<Entry<N, Object>> entries = adjacentNodeValues.entrySet().iterator();
- return new AbstractIterator<N>() {
- @Override
- protected N computeNext() {
- while (entries.hasNext()) {
- Entry<N, Object> entry = entries.next();
- if (isPredecessor(entry.getValue())) {
- return entry.getKey();
+ if (orderedNodeConnections == null) {
+ final Iterator<Entry<N, Object>> entries = adjacentNodeValues.entrySet().iterator();
+ return new AbstractIterator<N>() {
+ @Override
+ protected N computeNext() {
+ while (entries.hasNext()) {
+ Entry<N, Object> entry = entries.next();
+ if (isPredecessor(entry.getValue())) {
+ return entry.getKey();
+ }
}
+ return endOfData();
}
- return endOfData();
- }
- };
+ };
+ } else {
+ final Iterator<NodeConnection<N>> nodeConnections = orderedNodeConnections.iterator();
+ return new AbstractIterator<N>() {
+ @Override
+ protected N computeNext() {
+ while (nodeConnections.hasNext()) {
+ NodeConnection<N> nodeConnection = nodeConnections.next();
+ if (nodeConnection instanceof NodeConnection.Pred) {
+ return nodeConnection.node;
+ }
+ }
+ return endOfData();
+ }
+ };
+ }
}
@Override
@@ -137,19 +321,35 @@ final class DirectedGraphConnections<N, V> implements GraphConnections<N, V> {
return new AbstractSet<N>() {
@Override
public UnmodifiableIterator<N> iterator() {
- final Iterator<Entry<N, Object>> entries = adjacentNodeValues.entrySet().iterator();
- return new AbstractIterator<N>() {
- @Override
- protected N computeNext() {
- while (entries.hasNext()) {
- Entry<N, Object> entry = entries.next();
- if (isSuccessor(entry.getValue())) {
- return entry.getKey();
+ if (orderedNodeConnections == null) {
+ final Iterator<Entry<N, Object>> entries = adjacentNodeValues.entrySet().iterator();
+ return new AbstractIterator<N>() {
+ @Override
+ protected N computeNext() {
+ while (entries.hasNext()) {
+ Entry<N, Object> entry = entries.next();
+ if (isSuccessor(entry.getValue())) {
+ return entry.getKey();
+ }
}
+ return endOfData();
}
- return endOfData();
- }
- };
+ };
+ } else {
+ final Iterator<NodeConnection<N>> nodeConnections = orderedNodeConnections.iterator();
+ return new AbstractIterator<N>() {
+ @Override
+ protected N computeNext() {
+ while (nodeConnections.hasNext()) {
+ NodeConnection<N> nodeConnection = nodeConnections.next();
+ if (nodeConnection instanceof NodeConnection.Succ) {
+ return nodeConnection.node;
+ }
+ }
+ return endOfData();
+ }
+ };
+ }
}
@Override
@@ -164,9 +364,69 @@ final class DirectedGraphConnections<N, V> implements GraphConnections<N, V> {
};
}
+ @Override
+ public Iterator<EndpointPair<N>> incidentEdgeIterator(final N thisNode) {
+ checkNotNull(thisNode);
+
+ final Iterator<EndpointPair<N>> resultWithDoubleSelfLoop;
+ if (orderedNodeConnections == null) {
+ resultWithDoubleSelfLoop =
+ Iterators.concat(
+ Iterators.transform(
+ predecessors().iterator(),
+ new Function<N, EndpointPair<N>>() {
+ @Override
+ public EndpointPair<N> apply(N predecessor) {
+ return EndpointPair.ordered(predecessor, thisNode);
+ }
+ }),
+ Iterators.transform(
+ successors().iterator(),
+ new Function<N, EndpointPair<N>>() {
+ @Override
+ public EndpointPair<N> apply(N successor) {
+ return EndpointPair.ordered(thisNode, successor);
+ }
+ }));
+ } else {
+ resultWithDoubleSelfLoop =
+ Iterators.transform(
+ orderedNodeConnections.iterator(),
+ new Function<NodeConnection<N>, EndpointPair<N>>() {
+ @Override
+ public EndpointPair<N> apply(NodeConnection<N> connection) {
+ if (connection instanceof NodeConnection.Succ) {
+ return EndpointPair.ordered(thisNode, connection.node);
+ } else {
+ return EndpointPair.ordered(connection.node, thisNode);
+ }
+ }
+ });
+ }
+
+ final AtomicBoolean alreadySeenSelfLoop = new AtomicBoolean(false);
+ return new AbstractIterator<EndpointPair<N>>() {
+ @Override
+ protected EndpointPair<N> computeNext() {
+ while (resultWithDoubleSelfLoop.hasNext()) {
+ EndpointPair<N> edge = resultWithDoubleSelfLoop.next();
+ if (edge.nodeU().equals(edge.nodeV())) {
+ if (!alreadySeenSelfLoop.getAndSet(true)) {
+ return edge;
+ }
+ } else {
+ return edge;
+ }
+ }
+ return endOfData();
+ }
+ };
+ }
+
@SuppressWarnings("unchecked")
@Override
public V value(N node) {
+ checkNotNull(node);
Object value = adjacentNodeValues.get(node);
if (value == PRED) {
return null;
@@ -180,45 +440,83 @@ final class DirectedGraphConnections<N, V> implements GraphConnections<N, V> {
@SuppressWarnings("unchecked")
@Override
public void removePredecessor(N node) {
+ checkNotNull(node);
+
Object previousValue = adjacentNodeValues.get(node);
+ boolean removedPredecessor;
+
if (previousValue == PRED) {
adjacentNodeValues.remove(node);
- checkNonNegative(--predecessorCount);
+ removedPredecessor = true;
} else if (previousValue instanceof PredAndSucc) {
adjacentNodeValues.put((N) node, ((PredAndSucc) previousValue).successorValue);
+ removedPredecessor = true;
+ } else {
+ removedPredecessor = false;
+ }
+
+ if (removedPredecessor) {
checkNonNegative(--predecessorCount);
+
+ if (orderedNodeConnections != null) {
+ orderedNodeConnections.remove(new NodeConnection.Pred<>(node));
+ }
}
}
@SuppressWarnings("unchecked")
@Override
public V removeSuccessor(Object node) {
+ checkNotNull(node);
Object previousValue = adjacentNodeValues.get(node);
+ Object removedValue;
+
if (previousValue == null || previousValue == PRED) {
- return null;
+ removedValue = null;
} else if (previousValue instanceof PredAndSucc) {
adjacentNodeValues.put((N) node, PRED);
- checkNonNegative(--successorCount);
- return (V) ((PredAndSucc) previousValue).successorValue;
+ removedValue = ((PredAndSucc) previousValue).successorValue;
} else { // successor
adjacentNodeValues.remove(node);
+ removedValue = previousValue;
+ }
+
+ if (removedValue != null) {
checkNonNegative(--successorCount);
- return (V) previousValue;
+
+ if (orderedNodeConnections != null) {
+ orderedNodeConnections.remove(new NodeConnection.Succ<>((N) node));
+ }
}
+
+ return (V) removedValue;
}
@Override
public void addPredecessor(N node, V unused) {
Object previousValue = adjacentNodeValues.put(node, PRED);
+ boolean addedPredecessor;
+
if (previousValue == null) {
- checkPositive(++predecessorCount);
+ addedPredecessor = true;
} else if (previousValue instanceof PredAndSucc) {
// Restore previous PredAndSucc object.
adjacentNodeValues.put(node, previousValue);
+ addedPredecessor = false;
} else if (previousValue != PRED) { // successor
// Do NOT use method parameter value 'unused'. In directed graphs, successors store the value.
adjacentNodeValues.put(node, new PredAndSucc(previousValue));
+ addedPredecessor = true;
+ } else {
+ addedPredecessor = false;
+ }
+
+ if (addedPredecessor) {
checkPositive(++predecessorCount);
+
+ if (orderedNodeConnections != null) {
+ orderedNodeConnections.add(new NodeConnection.Pred<>(node));
+ }
}
}
@@ -226,19 +524,29 @@ final class DirectedGraphConnections<N, V> implements GraphConnections<N, V> {
@Override
public V addSuccessor(N node, V value) {
Object previousValue = adjacentNodeValues.put(node, value);
+ Object previousSuccessor;
+
if (previousValue == null) {
- checkPositive(++successorCount);
- return null;
+ previousSuccessor = null;
} else if (previousValue instanceof PredAndSucc) {
adjacentNodeValues.put(node, new PredAndSucc(value));
- return (V) ((PredAndSucc) previousValue).successorValue;
+ previousSuccessor = ((PredAndSucc) previousValue).successorValue;
} else if (previousValue == PRED) {
adjacentNodeValues.put(node, new PredAndSucc(value));
- checkPositive(++successorCount);
- return null;
+ previousSuccessor = null;
} else { // successor
- return (V) previousValue;
+ previousSuccessor = previousValue;
}
+
+ if (previousSuccessor == null) {
+ checkPositive(++successorCount);
+
+ if (orderedNodeConnections != null) {
+ orderedNodeConnections.add(new NodeConnection.Succ<>(node));
+ }
+ }
+
+ return (V) previousSuccessor;
}
private static boolean isPredecessor(@Nullable Object value) {