Skip to content

Commit

Permalink
Make mutable directed graphs about 40% smaller. Another nifty benefit…
Browse files Browse the repository at this point in the history
…: directedGraph.adjacentNodes(node).size() is now O(1). Remove (unused) equals/hashcode impl from NodeAdjacencies.

Interestingly, this does make immutable directed graphs slightly larger. The exact % depends on the degree of the nodes. inDegree == 1 and outDegree == 1 is a particularly large increase because two SingletonImmutableSets is much smaller than one ImmutableMap with two entries.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=120276536
  • Loading branch information
Bezier89 authored and cpovirk committed Apr 20, 2016
1 parent 660634d commit dd6b0e8
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 110 deletions.
15 changes: 8 additions & 7 deletions guava/src/com/google/common/graph/ConfigurableGraph.java
Expand Up @@ -103,16 +103,17 @@ public boolean addEdge(N node1, N node2) {
@CanIgnoreReturnValue @CanIgnoreReturnValue
public boolean removeNode(Object node) { public boolean removeNode(Object node) {
checkNotNull(node, "node"); checkNotNull(node, "node");
if (!nodes().contains(node)) { NodeAdjacencies<N> connections = nodeConnections.get(node);
if (connections == null) {
return false; return false;
} }
for (N successor : nodeConnections.get(node).successors()) { for (N successor : connections.successors()) {
if (!node.equals(successor)) { if (!node.equals(successor)) {
// don't remove the successor if it's the input node (=> CME); will be removed below // don't remove the successor if it's the input node (=> CME); will be removed below
nodeConnections.get(successor).removePredecessor(node); nodeConnections.get(successor).removePredecessor(node);
} }
} }
for (N predecessor : nodeConnections.get(node).predecessors()) { for (N predecessor : connections.predecessors()) {
nodeConnections.get(predecessor).removeSuccessor(node); nodeConnections.get(predecessor).removeSuccessor(node);
} }
nodeConnections.remove(node); nodeConnections.remove(node);
Expand All @@ -125,13 +126,13 @@ public boolean removeEdge(Object node1, Object node2) {
checkNotNull(node1, "node1"); checkNotNull(node1, "node1");
checkNotNull(node2, "node2"); checkNotNull(node2, "node2");
NodeAdjacencies<N> connectionsN1 = nodeConnections.get(node1); NodeAdjacencies<N> connectionsN1 = nodeConnections.get(node1);
NodeAdjacencies<N> connectionsN2 = nodeConnections.get(node2); if (connectionsN1 == null || !connectionsN1.successors().contains(node2)) {
if (connectionsN1 == null || connectionsN2 == null) {
return false; return false;
} }
boolean result = connectionsN1.removeSuccessor(node2); NodeAdjacencies<N> connectionsN2 = nodeConnections.get(node2);
connectionsN1.removeSuccessor(node2);
connectionsN2.removePredecessor(node1); connectionsN2.removePredecessor(node1);
return result; return true;
} }


private NodeAdjacencies<N> newNodeConnections() { private NodeAdjacencies<N> newNodeConnections() {
Expand Down
163 changes: 117 additions & 46 deletions guava/src/com/google/common/graph/DirectedNodeAdjacencies.java
Expand Up @@ -17,102 +17,173 @@
package com.google.common.graph; package com.google.common.graph;


import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.graph.GraphConstants.EXPECTED_DEGREE;


import com.google.common.base.MoreObjects; import com.google.common.base.Predicate;
import com.google.common.base.Objects; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterators;
import com.google.common.collect.Sets; import com.google.common.collect.Maps;


import java.util.AbstractSet;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set; import java.util.Set;


import javax.annotation.Nullable;

/** /**
* A class representing an origin node's adjacent nodes in a directed graph. * A class representing an origin node's adjacent nodes in a directed graph.
* *
* @author James Sexton * @author James Sexton
* @param <N> Node parameter type * @param <N> Node parameter type
*/ */
final class DirectedNodeAdjacencies<N> implements NodeAdjacencies<N> { final class DirectedNodeAdjacencies<N> implements NodeAdjacencies<N> {
private final Set<N> predecessors; enum Adjacency {
private final Set<N> successors; PRED,
SUCC,
BOTH;
}

private final Map<N, Adjacency> adjacentNodes;

private int predecessorCount;
private int successorCount;


private DirectedNodeAdjacencies(Set<N> predecessors, Set<N> successors) { private DirectedNodeAdjacencies(
this.predecessors = checkNotNull(predecessors, "predecessors"); Map<N, Adjacency> adjacentNodes, int predecessorCount, int successorCount) {
this.successors = checkNotNull(successors, "successors"); this.adjacentNodes = checkNotNull(adjacentNodes, "adjacentNodes");
this.predecessorCount = predecessorCount;
this.successorCount = successorCount;
} }


static <N> DirectedNodeAdjacencies<N> of() { static <N> DirectedNodeAdjacencies<N> of() {
// TODO(user): Enable users to specify the expected number of neighbors of a new node. return new DirectedNodeAdjacencies<N>(
return new DirectedNodeAdjacencies<N>(Sets.<N>newHashSet(), Sets.<N>newHashSet()); Maps.<N, Adjacency>newHashMapWithExpectedSize(EXPECTED_DEGREE), 0, 0);
} }


static <N> DirectedNodeAdjacencies<N> ofImmutable(Set<N> predecessors, Set<N> successors) { static <N> DirectedNodeAdjacencies<N> ofImmutable(
Map<N, Adjacency> adjacentNodes, int predecessorCount, int successorCount) {
return new DirectedNodeAdjacencies<N>( return new DirectedNodeAdjacencies<N>(
ImmutableSet.copyOf(predecessors), ImmutableSet.copyOf(successors)); ImmutableMap.copyOf(adjacentNodes), predecessorCount, successorCount);
} }


@Override @Override
public Set<N> adjacentNodes() { public Set<N> adjacentNodes() {
return Sets.union(predecessors(), successors()); return Collections.unmodifiableSet(adjacentNodes.keySet());
} }


@Override @Override
public Set<N> predecessors() { public Set<N> predecessors() {
return Collections.unmodifiableSet(predecessors); // Don't simply use Sets.filter() or we'll end up with O(N) instead of O(1) size().
return new AbstractSet<N>() {
@Override
public Iterator<N> iterator() {
return Iterators.filter(adjacentNodes().iterator(), new Predicate<N>() {
@Override
public boolean apply(N node) {
return isPredecessor(node);
}
});
}

@Override
public int size() {
return predecessorCount;
}

@Override
public boolean contains(Object o) {
return isPredecessor(o);
}
};
} }


@Override @Override
public Set<N> successors() { public Set<N> successors() {
return Collections.unmodifiableSet(successors); // Don't simply use Sets.filter() or we'll end up with O(N) instead of O(1) size().
return new AbstractSet<N>() {
@Override
public Iterator<N> iterator() {
return Iterators.filter(adjacentNodes().iterator(), new Predicate<N>() {
@Override
public boolean apply(N node) {
return isSuccessor(node);
}
});
}

@Override
public int size() {
return successorCount;
}

@Override
public boolean contains(Object o) {
return isSuccessor(o);
}
};
} }


@SuppressWarnings("unchecked") // Safe because we only cast if node is a key of Map<N, Adjacency>
@Override @Override
public boolean removePredecessor(Object node) { public void removePredecessor(Object node) {
checkNotNull(node, "node"); checkNotNull(node, "node");
return predecessors.remove(node); Adjacency adjacency = adjacentNodes.get(node);
if (adjacency == Adjacency.BOTH) {
adjacentNodes.put((N) node, Adjacency.SUCC);
predecessorCount--;
} else if (adjacency == Adjacency.PRED) {
adjacentNodes.remove(node);
predecessorCount--;
}
} }


@SuppressWarnings("unchecked") // Safe because we only cast if node is a key of Map<N, Adjacency>
@Override @Override
public boolean removeSuccessor(Object node) { public void removeSuccessor(Object node) {
checkNotNull(node, "node"); checkNotNull(node, "node");
return successors.remove(node); Adjacency adjacency = adjacentNodes.get(node);
if (adjacency == Adjacency.BOTH) {
adjacentNodes.put((N) node, Adjacency.PRED);
successorCount--;
} else if (adjacency == Adjacency.SUCC) {
adjacentNodes.remove(node);
successorCount--;
}
} }


@Override @Override
public boolean addPredecessor(N node) { public void addPredecessor(N node) {
checkNotNull(node, "node"); checkNotNull(node, "node");
return predecessors.add(node); Adjacency adjacency = adjacentNodes.get(node);
if (adjacency == null) {
adjacentNodes.put(node, Adjacency.PRED);
predecessorCount++;
} else if (adjacency == Adjacency.SUCC) {
adjacentNodes.put(node, Adjacency.BOTH);
predecessorCount++;
}
} }


@Override @Override
public boolean addSuccessor(N node) { public void addSuccessor(N node) {
checkNotNull(node, "node"); checkNotNull(node, "node");
return successors.add(node); Adjacency adjacency = adjacentNodes.get(node);
if (adjacency == null) {
adjacentNodes.put(node, Adjacency.SUCC);
successorCount++;
} else if (adjacency == Adjacency.PRED) {
adjacentNodes.put(node, Adjacency.BOTH);
successorCount++;
}
} }


// For now, hashCode() and equals() are unused by any graph implementation. private boolean isPredecessor(Object node) {
@Override Adjacency adjacency = adjacentNodes.get(node);
public int hashCode() { return (adjacency == Adjacency.PRED || adjacency == Adjacency.BOTH);
return Objects.hashCode(predecessors, successors);
} }


@Override private boolean isSuccessor(Object node) {
public boolean equals(@Nullable Object object) { Adjacency adjacency = adjacentNodes.get(node);
if (object instanceof DirectedNodeAdjacencies) { return (adjacency == Adjacency.SUCC || adjacency == Adjacency.BOTH);
DirectedNodeAdjacencies<?> that = (DirectedNodeAdjacencies<?>) object;
return this.predecessors.equals(that.predecessors)
&& this.successors.equals(that.successors);
}
return false;
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("predecessors", predecessors)
.add("successors", successors)
.toString();
} }
} }
31 changes: 28 additions & 3 deletions guava/src/com/google/common/graph/ImmutableGraph.java
Expand Up @@ -20,8 +20,9 @@
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;


import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.graph.DirectedNodeAdjacencies.Adjacency;


import java.util.Map; import java.util.Set;


/** /**
* A {@link Graph} whose relationships are constant. Instances of this class may be obtained * A {@link Graph} whose relationships are constant. Instances of this class may be obtained
Expand Down Expand Up @@ -61,7 +62,7 @@ public static <N> ImmutableGraph<N> copyOf(ImmutableGraph<N> graph) {
return checkNotNull(graph); return checkNotNull(graph);
} }


private static <N> Map<N, NodeAdjacencies<N>> getNodeConnections(Graph<N> graph) { private static <N> ImmutableMap<N, NodeAdjacencies<N>> getNodeConnections(Graph<N> graph) {
ImmutableMap.Builder<N, NodeAdjacencies<N>> nodeConnections = ImmutableMap.builder(); ImmutableMap.Builder<N, NodeAdjacencies<N>> nodeConnections = ImmutableMap.builder();
for (N node : graph.nodes()) { for (N node : graph.nodes()) {
nodeConnections.put(node, nodeConnectionsOf(graph, node)); nodeConnections.put(node, nodeConnectionsOf(graph, node));
Expand All @@ -71,7 +72,31 @@ private static <N> Map<N, NodeAdjacencies<N>> getNodeConnections(Graph<N> graph)


private static <N> NodeAdjacencies<N> nodeConnectionsOf(Graph<N> graph, N node) { private static <N> NodeAdjacencies<N> nodeConnectionsOf(Graph<N> graph, N node) {
return graph.isDirected() return graph.isDirected()
? DirectedNodeAdjacencies.ofImmutable(graph.predecessors(node), graph.successors(node)) ? DirectedNodeAdjacencies.ofImmutable(createAdjacencyMap(
graph, node), graph.predecessors(node).size(), graph.successors(node).size())
: UndirectedNodeAdjacencies.ofImmutable(graph.adjacentNodes(node)); : UndirectedNodeAdjacencies.ofImmutable(graph.adjacentNodes(node));
} }

private static <N> ImmutableMap<N, Adjacency> createAdjacencyMap(Graph<N> graph, N node) {
Set<N> predecessors = graph.predecessors(node);
Set<N> successors = graph.successors(node);
ImmutableMap.Builder<N, Adjacency> nodeAdjacencies = ImmutableMap.builder();
for (N adjacentNode : graph.adjacentNodes(node)) {
nodeAdjacencies.put(adjacentNode,
getAdjacency(predecessors.contains(adjacentNode), successors.contains(adjacentNode)));
}
return nodeAdjacencies.build();
}

private static Adjacency getAdjacency(boolean isPredecessor, boolean isSuccesor) {
if (isPredecessor && isSuccesor) {
return Adjacency.BOTH;
} else if (isPredecessor) {
return Adjacency.PRED;
} else if (isSuccesor) {
return Adjacency.SUCC;
} else {
throw new IllegalStateException();
}
}
} }
24 changes: 5 additions & 19 deletions guava/src/com/google/common/graph/NodeAdjacencies.java
Expand Up @@ -16,11 +16,9 @@


package com.google.common.graph; package com.google.common.graph;


import com.google.errorprone.annotations.CanIgnoreReturnValue;

import java.util.Set; import java.util.Set;
/** /**
* An interface for representing an origin node's adjacent nodes in a network. * An interface for representing an origin node's adjacent nodes in a graph.
* *
* @author James Sexton * @author James Sexton
* @param <N> Node parameter type * @param <N> Node parameter type
Expand All @@ -35,35 +33,23 @@ interface NodeAdjacencies<N> {


/** /**
* Remove {@code node} from the set of predecessors. * Remove {@code node} from the set of predecessors.
*
* @return true iff the adjacency relationships changed
*/ */
@CanIgnoreReturnValue void removePredecessor(Object node);
boolean removePredecessor(Object node);


/** /**
* Remove {@code node} from the set of successors. * Remove {@code node} from the set of successors.
*
* @return true iff the adjacency relationships changed
*/ */
@CanIgnoreReturnValue void removeSuccessor(Object node);
boolean removeSuccessor(Object node);


/** /**
* Add {@code node} as a predecessor to the origin node. * Add {@code node} as a predecessor to the origin node.
* In the case of an undirected graph, it also becomes a successor. * In the case of an undirected graph, it also becomes a successor.
*
* @return true iff the adjacency relationships changed
*/ */
@CanIgnoreReturnValue void addPredecessor(N node);
boolean addPredecessor(N node);


/** /**
* Add {@code node} as a successor to the origin node. * Add {@code node} as a successor to the origin node.
* In the case of an undirected graph, it also becomes a predecessor. * In the case of an undirected graph, it also becomes a predecessor.
*
* @return true iff the adjacency relationships changed
*/ */
@CanIgnoreReturnValue void addSuccessor(N node);
boolean addSuccessor(N node);
} }

0 comments on commit dd6b0e8

Please sign in to comment.