Skip to content

Commit

Permalink
common.graph: add incidentEdges() method to Graph/ValueGraph.
Browse files Browse the repository at this point in the history
Also added an override of remove() to the Set returned by the edges() method, to ensure that it wouldn't allow removal of elements from the set.

Java []

RELNOTES=Graph, ValueGraph: added incidentEdges() method.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=181529878
  • Loading branch information
jrtom authored and cpovirk committed Jan 11, 2018
1 parent 7462c09 commit d788bc1
Show file tree
Hide file tree
Showing 22 changed files with 492 additions and 4 deletions.
Expand Up @@ -44,6 +44,14 @@ public void successors_oneEdge() {
assertThat(graph.successors(N2)).isEmpty();
}

@Test
public void incidentEdges_oneEdge() {
putEdge(N1, N2);
EndpointPair<Integer> expectedEndpoints = EndpointPair.ordered(N1, N2);
assertThat(graph.incidentEdges(N1)).containsExactly(expectedEndpoints);
assertThat(graph.incidentEdges(N2)).containsExactly(expectedEndpoints);
}

@Test
public void inDegree_oneEdge() {
putEdge(N1, N2);
Expand Down
Expand Up @@ -156,12 +156,22 @@ static <N> void validateGraph(Graph<N> graph) {
for (N predecessor : sanityCheckSet(graph.predecessors(node))) {
assertThat(graph.successors(predecessor)).contains(node);
assertThat(graph.hasEdgeConnecting(predecessor, node)).isTrue();
assertThat(graph.incidentEdges(node)).contains(EndpointPair.of(graph, predecessor, node));
}

for (N successor : sanityCheckSet(graph.successors(node))) {
allEndpointPairs.add(EndpointPair.of(graph, node, successor));
assertThat(graph.predecessors(successor)).contains(node);
assertThat(graph.hasEdgeConnecting(node, successor)).isTrue();
assertThat(graph.incidentEdges(node)).contains(EndpointPair.of(graph, node, successor));
}

for (EndpointPair<N> endpoints : sanityCheckSet(graph.incidentEdges(node))) {
if (graph.isDirected()) {
assertThat(graph.hasEdgeConnecting(endpoints.source(), endpoints.target())).isTrue();
} else {
assertThat(graph.hasEdgeConnecting(endpoints.nodeU(), endpoints.nodeV())).isTrue();
}
}
}

Expand Down Expand Up @@ -198,6 +208,13 @@ static <N> void validateGraph(Graph<N> graph) {
@Test
public abstract void successors_checkReturnedSetMutability();

/**
* Verifies that the {@code Set} returned by {@code incidentEdges} has the expected mutability
* property (see the {@code Graph} documentation for more information).
*/
@Test
public abstract void incidentEdges_checkReturnedSetMutability();

@Test
public void nodes_oneNode() {
addNode(N1);
Expand Down Expand Up @@ -264,6 +281,22 @@ public void successors_nodeNotInGraph() {
}
}

@Test
public void incidentEdges_noIncidentEdges() {
addNode(N1);
assertThat(graph.incidentEdges(N1)).isEmpty();
}

@Test
public void incidentEdges_nodeNotInGraph() {
try {
graph.incidentEdges(NODE_NOT_IN_GRAPH);
fail(ERROR_NODE_NOT_IN_GRAPH);
} catch (IllegalArgumentException e) {
assertNodeNotInGraphErrorMessage(e);
}
}

@Test
public void degree_oneEdge() {
putEdge(N1, N2);
Expand Down
Expand Up @@ -55,6 +55,14 @@ public void successors_oneEdge() {
assertThat(graph.successors(N2)).containsExactly(N1);
}

@Test
public void incidentEdges_oneEdge() {
putEdge(N1, N2);
EndpointPair<Integer> expectedEndpoints = EndpointPair.unordered(N1, N2);
assertThat(graph.incidentEdges(N1)).containsExactly(expectedEndpoints);
assertThat(graph.incidentEdges(N2)).containsExactly(expectedEndpoints);
}

@Test
public void inDegree_oneEdge() {
putEdge(N1, N2);
Expand Down
Expand Up @@ -54,6 +54,16 @@ public void successors_selfLoop() {
assertThat(graph.successors(N1)).containsExactly(N1, N2);
}

@Test
public void incidentEdges_selfLoop() {
putEdge(N1, N1);
assertThat(graph.incidentEdges(N1)).containsExactly(EndpointPair.ordered(N1, N1));
putEdge(N1, N2);
assertThat(graph.incidentEdges(N1)).containsExactly(
EndpointPair.ordered(N1, N1),
EndpointPair.ordered(N1, N2));
}

@Test
public void degree_selfLoop() {
putEdge(N1, N1);
Expand Down
Expand Up @@ -92,6 +92,20 @@ public void successors_checkReturnedSetMutability() {
}
}

@Override
@Test
public void incidentEdges_checkReturnedSetMutability() {
addNode(N1);
Set<EndpointPair<Integer>> incidentEdges = graph.incidentEdges(N1);
try {
incidentEdges.add(EndpointPair.ordered(N1, N2));
fail(ERROR_MODIFIABLE_SET);
} catch (UnsupportedOperationException e) {
putEdge(N1, N2);
assertThat(incidentEdges).containsExactlyElementsIn(graph.incidentEdges(N1));
}
}

// Element Mutation

@Test
Expand Down
Expand Up @@ -92,6 +92,20 @@ public void successors_checkReturnedSetMutability() {
}
}

@Override
@Test
public void incidentEdges_checkReturnedSetMutability() {
addNode(N1);
Set<EndpointPair<Integer>> incidentEdges = graph.incidentEdges(N1);
try {
incidentEdges.add(EndpointPair.unordered(N1, N2));
fail(ERROR_MODIFIABLE_SET);
} catch (UnsupportedOperationException e) {
putEdge(N1, N2);
assertThat(incidentEdges).containsExactlyElementsIn(graph.incidentEdges(N1));
}
}

// Element Mutation

@Test
Expand Down
Expand Up @@ -54,6 +54,16 @@ public void successors_selfLoop() {
assertThat(graph.successors(N1)).containsExactly(N1, N2);
}

@Test
public void incidentEdges_selfLoop() {
putEdge(N1, N1);
assertThat(graph.incidentEdges(N1)).containsExactly(EndpointPair.unordered(N1, N1));
putEdge(N1, N2);
assertThat(graph.incidentEdges(N1)).containsExactly(
EndpointPair.unordered(N1, N1),
EndpointPair.unordered(N1, N2));
}

@Test
public void degree_selfLoop() {
putEdge(N1, N1);
Expand Down
132 changes: 132 additions & 0 deletions android/guava/src/com/google/common/graph/AbstractBaseGraph.java
Expand Up @@ -16,9 +16,14 @@

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 com.google.common.base.Function;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.math.IntMath;
import com.google.common.primitives.Ints;
Expand Down Expand Up @@ -69,6 +74,11 @@ public int size() {
return Ints.saturatedCast(edgeCount());
}

@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}

// Mostly safe: We check contains(u) before calling successors(u), so we perform unsafe
// operations only in weird cases like checking for an EndpointPair<ArrayList> in a
// Graph<LinkedList>.
Expand All @@ -86,6 +96,13 @@ && nodes().contains(endpointPair.nodeU())
};
}

@Override
public Set<EndpointPair<N>> incidentEdges(N node) {
checkNotNull(node);
checkArgument(nodes().contains(node), "Node %s is not an element of this graph.", node);
return IncidentEdgeSet.of(this, node);
}

@Override
public int degree(N node) {
if (isDirected()) {
Expand Down Expand Up @@ -113,4 +130,119 @@ public boolean hasEdgeConnecting(N nodeU, N nodeV) {
checkNotNull(nodeV);
return nodes().contains(nodeU) && successors(nodeU).contains(nodeV);
}

private abstract static class IncidentEdgeSet<N> extends AbstractSet<EndpointPair<N>> {
protected final N node;
protected final BaseGraph<N> graph;

public static <N> IncidentEdgeSet<N> of(BaseGraph<N> graph, N node) {
return graph.isDirected() ? new Directed<>(graph, node) : new Undirected<>(graph, node);
}

private IncidentEdgeSet(BaseGraph<N> graph, N node) {
this.graph = graph;
this.node = node;
}

@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}

private static final class Directed<N> extends IncidentEdgeSet<N> {

private Directed(BaseGraph<N> graph, N node) {
super(graph, node);
}

@Override
public UnmodifiableIterator<EndpointPair<N>> iterator() {
return Iterators.unmodifiableIterator(
Iterators.concat(
Iterators.transform(
graph.predecessors(node).iterator(),
new Function<N, EndpointPair<N>>() {
@Override
public EndpointPair<N> apply(N predecessor) {
return EndpointPair.ordered(predecessor, node);
}
}),
Iterators.transform(
// filter out 'node' from successors (already covered by predecessors, above)
Sets.difference(graph.successors(node), ImmutableSet.of(node)).iterator(),
new Function<N, EndpointPair<N>>() {
@Override
public EndpointPair<N> apply(N successor) {
return EndpointPair.ordered(node, successor);
}
})));
}

@Override
public int size() {
return graph.inDegree(node)
+ graph.outDegree(node)
- (graph.successors(node).contains(node) ? 1 : 0);
}

@Override
public boolean contains(@NullableDecl Object obj) {
if (!(obj instanceof EndpointPair)) {
return false;
}

EndpointPair<?> endpointPair = (EndpointPair<?>) obj;
if (!endpointPair.isOrdered()) {
return false;
}

Object source = endpointPair.source();
Object target = endpointPair.target();
return (node.equals(source) && graph.successors(node).contains(target))
|| (node.equals(target) && graph.predecessors(node).contains(source));
}
}

private static final class Undirected<N> extends IncidentEdgeSet<N> {
private Undirected(BaseGraph<N> graph, N node) {
super(graph, node);
}

@Override
public UnmodifiableIterator<EndpointPair<N>> iterator() {
return Iterators.unmodifiableIterator(
Iterators.transform(
graph.adjacentNodes(node).iterator(),
new Function<N, EndpointPair<N>>() {
@Override
public EndpointPair<N> apply(N adjacentNode) {
return EndpointPair.unordered(node, adjacentNode);
}
}));
}

@Override
public int size() {
return graph.adjacentNodes(node).size();
}

@Override
public boolean contains(@NullableDecl Object obj) {
if (!(obj instanceof EndpointPair)) {
return false;
}

EndpointPair<?> endpointPair = (EndpointPair<?>) obj;
if (endpointPair.isOrdered()) {
return false;
}
Set<N> adjacent = graph.adjacentNodes(node);
Object nodeU = endpointPair.nodeU();
Object nodeV = endpointPair.nodeV();

return (node.equals(nodeV) && adjacent.contains(nodeU))
|| (node.equals(nodeU) && adjacent.contains(nodeV));
}
}
}
}
11 changes: 9 additions & 2 deletions android/guava/src/com/google/common/graph/BaseGraph.java
Expand Up @@ -92,14 +92,21 @@ interface BaseGraph<N> extends SuccessorsFunction<N>, PredecessorsFunction<N> {
@Override
Set<N> successors(N node);

/**
* Returns the edges in this graph whose endpoints include {@code node}.
*
* @throws IllegalArgumentException if {@code node} is not an element of this graph
*/
Set<EndpointPair<N>> incidentEdges(N node);

/**
* Returns the count of {@code node}'s incident edges, counting self-loops twice (equivalently,
* the number of times an edge touches {@code node}).
*
* <p>For directed graphs, this is equal to {@code inDegree(node) + outDegree(node)}.
*
* <p>For undirected graphs, this is equal to {@code adjacentNodes(node).size()} + (1 if {@code
* node} has an incident self-loop, 0 otherwise).
* <p>For undirected graphs, this is equal to {@code incidentEdges(node).size()} + (number of
* self-loops incident to {@code node}).
*
* <p>If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}.
*
Expand Down
4 changes: 4 additions & 0 deletions android/guava/src/com/google/common/graph/Graph.java
Expand Up @@ -145,6 +145,10 @@ public interface Graph<N> extends BaseGraph<N> {
@Override
Set<N> successors(N node);

/** {@inheritDoc} */
@Override
Set<EndpointPair<N>> incidentEdges(N node);

/** {@inheritDoc} */
@Override
int degree(N node);
Expand Down
4 changes: 4 additions & 0 deletions android/guava/src/com/google/common/graph/ValueGraph.java
Expand Up @@ -157,6 +157,10 @@ public interface ValueGraph<N, V> extends BaseGraph<N> {
@Override
Set<N> successors(N node);

/** {@inheritDoc} */
@Override
Set<EndpointPair<N>> incidentEdges(N node);

/** {@inheritDoc} */
@Override
int degree(N node);
Expand Down
Expand Up @@ -44,6 +44,14 @@ public void successors_oneEdge() {
assertThat(graph.successors(N2)).isEmpty();
}

@Test
public void incidentEdges_oneEdge() {
putEdge(N1, N2);
EndpointPair<Integer> expectedEndpoints = EndpointPair.ordered(N1, N2);
assertThat(graph.incidentEdges(N1)).containsExactly(expectedEndpoints);
assertThat(graph.incidentEdges(N2)).containsExactly(expectedEndpoints);
}

@Test
public void inDegree_oneEdge() {
putEdge(N1, N2);
Expand Down

1 comment on commit d788bc1

@jbduncan
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Please sign in to comment.