Skip to content

Commit d788bc1

Browse files
jrtomcpovirk
authored andcommitted
common.graph: add incidentEdges() method to Graph/ValueGraph.
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
1 parent 7462c09 commit d788bc1

22 files changed

+492
-4
lines changed

android/guava-tests/test/com/google/common/graph/AbstractDirectedGraphTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ public void successors_oneEdge() {
4444
assertThat(graph.successors(N2)).isEmpty();
4545
}
4646

47+
@Test
48+
public void incidentEdges_oneEdge() {
49+
putEdge(N1, N2);
50+
EndpointPair<Integer> expectedEndpoints = EndpointPair.ordered(N1, N2);
51+
assertThat(graph.incidentEdges(N1)).containsExactly(expectedEndpoints);
52+
assertThat(graph.incidentEdges(N2)).containsExactly(expectedEndpoints);
53+
}
54+
4755
@Test
4856
public void inDegree_oneEdge() {
4957
putEdge(N1, N2);

android/guava-tests/test/com/google/common/graph/AbstractGraphTest.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,22 @@ static <N> void validateGraph(Graph<N> graph) {
156156
for (N predecessor : sanityCheckSet(graph.predecessors(node))) {
157157
assertThat(graph.successors(predecessor)).contains(node);
158158
assertThat(graph.hasEdgeConnecting(predecessor, node)).isTrue();
159+
assertThat(graph.incidentEdges(node)).contains(EndpointPair.of(graph, predecessor, node));
159160
}
160161

161162
for (N successor : sanityCheckSet(graph.successors(node))) {
162163
allEndpointPairs.add(EndpointPair.of(graph, node, successor));
163164
assertThat(graph.predecessors(successor)).contains(node);
164165
assertThat(graph.hasEdgeConnecting(node, successor)).isTrue();
166+
assertThat(graph.incidentEdges(node)).contains(EndpointPair.of(graph, node, successor));
167+
}
168+
169+
for (EndpointPair<N> endpoints : sanityCheckSet(graph.incidentEdges(node))) {
170+
if (graph.isDirected()) {
171+
assertThat(graph.hasEdgeConnecting(endpoints.source(), endpoints.target())).isTrue();
172+
} else {
173+
assertThat(graph.hasEdgeConnecting(endpoints.nodeU(), endpoints.nodeV())).isTrue();
174+
}
165175
}
166176
}
167177

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

211+
/**
212+
* Verifies that the {@code Set} returned by {@code incidentEdges} has the expected mutability
213+
* property (see the {@code Graph} documentation for more information).
214+
*/
215+
@Test
216+
public abstract void incidentEdges_checkReturnedSetMutability();
217+
201218
@Test
202219
public void nodes_oneNode() {
203220
addNode(N1);
@@ -264,6 +281,22 @@ public void successors_nodeNotInGraph() {
264281
}
265282
}
266283

284+
@Test
285+
public void incidentEdges_noIncidentEdges() {
286+
addNode(N1);
287+
assertThat(graph.incidentEdges(N1)).isEmpty();
288+
}
289+
290+
@Test
291+
public void incidentEdges_nodeNotInGraph() {
292+
try {
293+
graph.incidentEdges(NODE_NOT_IN_GRAPH);
294+
fail(ERROR_NODE_NOT_IN_GRAPH);
295+
} catch (IllegalArgumentException e) {
296+
assertNodeNotInGraphErrorMessage(e);
297+
}
298+
}
299+
267300
@Test
268301
public void degree_oneEdge() {
269302
putEdge(N1, N2);

android/guava-tests/test/com/google/common/graph/AbstractUndirectedGraphTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ public void successors_oneEdge() {
5555
assertThat(graph.successors(N2)).containsExactly(N1);
5656
}
5757

58+
@Test
59+
public void incidentEdges_oneEdge() {
60+
putEdge(N1, N2);
61+
EndpointPair<Integer> expectedEndpoints = EndpointPair.unordered(N1, N2);
62+
assertThat(graph.incidentEdges(N1)).containsExactly(expectedEndpoints);
63+
assertThat(graph.incidentEdges(N2)).containsExactly(expectedEndpoints);
64+
}
65+
5866
@Test
5967
public void inDegree_oneEdge() {
6068
putEdge(N1, N2);

android/guava-tests/test/com/google/common/graph/ConfigurableDirectedGraphTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ public void successors_selfLoop() {
5454
assertThat(graph.successors(N1)).containsExactly(N1, N2);
5555
}
5656

57+
@Test
58+
public void incidentEdges_selfLoop() {
59+
putEdge(N1, N1);
60+
assertThat(graph.incidentEdges(N1)).containsExactly(EndpointPair.ordered(N1, N1));
61+
putEdge(N1, N2);
62+
assertThat(graph.incidentEdges(N1)).containsExactly(
63+
EndpointPair.ordered(N1, N1),
64+
EndpointPair.ordered(N1, N2));
65+
}
66+
5767
@Test
5868
public void degree_selfLoop() {
5969
putEdge(N1, N1);

android/guava-tests/test/com/google/common/graph/ConfigurableSimpleDirectedGraphTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ public void successors_checkReturnedSetMutability() {
9292
}
9393
}
9494

95+
@Override
96+
@Test
97+
public void incidentEdges_checkReturnedSetMutability() {
98+
addNode(N1);
99+
Set<EndpointPair<Integer>> incidentEdges = graph.incidentEdges(N1);
100+
try {
101+
incidentEdges.add(EndpointPair.ordered(N1, N2));
102+
fail(ERROR_MODIFIABLE_SET);
103+
} catch (UnsupportedOperationException e) {
104+
putEdge(N1, N2);
105+
assertThat(incidentEdges).containsExactlyElementsIn(graph.incidentEdges(N1));
106+
}
107+
}
108+
95109
// Element Mutation
96110

97111
@Test

android/guava-tests/test/com/google/common/graph/ConfigurableSimpleUndirectedGraphTest.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,20 @@ public void successors_checkReturnedSetMutability() {
9292
}
9393
}
9494

95+
@Override
96+
@Test
97+
public void incidentEdges_checkReturnedSetMutability() {
98+
addNode(N1);
99+
Set<EndpointPair<Integer>> incidentEdges = graph.incidentEdges(N1);
100+
try {
101+
incidentEdges.add(EndpointPair.unordered(N1, N2));
102+
fail(ERROR_MODIFIABLE_SET);
103+
} catch (UnsupportedOperationException e) {
104+
putEdge(N1, N2);
105+
assertThat(incidentEdges).containsExactlyElementsIn(graph.incidentEdges(N1));
106+
}
107+
}
108+
95109
// Element Mutation
96110

97111
@Test

android/guava-tests/test/com/google/common/graph/ConfigurableUndirectedGraphTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ public void successors_selfLoop() {
5454
assertThat(graph.successors(N1)).containsExactly(N1, N2);
5555
}
5656

57+
@Test
58+
public void incidentEdges_selfLoop() {
59+
putEdge(N1, N1);
60+
assertThat(graph.incidentEdges(N1)).containsExactly(EndpointPair.unordered(N1, N1));
61+
putEdge(N1, N2);
62+
assertThat(graph.incidentEdges(N1)).containsExactly(
63+
EndpointPair.unordered(N1, N1),
64+
EndpointPair.unordered(N1, N2));
65+
}
66+
5767
@Test
5868
public void degree_selfLoop() {
5969
putEdge(N1, N1);

android/guava/src/com/google/common/graph/AbstractBaseGraph.java

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@
1616

1717
package com.google.common.graph;
1818

19+
import static com.google.common.base.Preconditions.checkArgument;
1920
import static com.google.common.base.Preconditions.checkNotNull;
2021
import static com.google.common.base.Preconditions.checkState;
2122

23+
import com.google.common.base.Function;
24+
import com.google.common.collect.ImmutableSet;
25+
import com.google.common.collect.Iterators;
26+
import com.google.common.collect.Sets;
2227
import com.google.common.collect.UnmodifiableIterator;
2328
import com.google.common.math.IntMath;
2429
import com.google.common.primitives.Ints;
@@ -69,6 +74,11 @@ public int size() {
6974
return Ints.saturatedCast(edgeCount());
7075
}
7176

77+
@Override
78+
public boolean remove(Object o) {
79+
throw new UnsupportedOperationException();
80+
}
81+
7282
// Mostly safe: We check contains(u) before calling successors(u), so we perform unsafe
7383
// operations only in weird cases like checking for an EndpointPair<ArrayList> in a
7484
// Graph<LinkedList>.
@@ -86,6 +96,13 @@ && nodes().contains(endpointPair.nodeU())
8696
};
8797
}
8898

99+
@Override
100+
public Set<EndpointPair<N>> incidentEdges(N node) {
101+
checkNotNull(node);
102+
checkArgument(nodes().contains(node), "Node %s is not an element of this graph.", node);
103+
return IncidentEdgeSet.of(this, node);
104+
}
105+
89106
@Override
90107
public int degree(N node) {
91108
if (isDirected()) {
@@ -113,4 +130,119 @@ public boolean hasEdgeConnecting(N nodeU, N nodeV) {
113130
checkNotNull(nodeV);
114131
return nodes().contains(nodeU) && successors(nodeU).contains(nodeV);
115132
}
133+
134+
private abstract static class IncidentEdgeSet<N> extends AbstractSet<EndpointPair<N>> {
135+
protected final N node;
136+
protected final BaseGraph<N> graph;
137+
138+
public static <N> IncidentEdgeSet<N> of(BaseGraph<N> graph, N node) {
139+
return graph.isDirected() ? new Directed<>(graph, node) : new Undirected<>(graph, node);
140+
}
141+
142+
private IncidentEdgeSet(BaseGraph<N> graph, N node) {
143+
this.graph = graph;
144+
this.node = node;
145+
}
146+
147+
@Override
148+
public boolean remove(Object o) {
149+
throw new UnsupportedOperationException();
150+
}
151+
152+
private static final class Directed<N> extends IncidentEdgeSet<N> {
153+
154+
private Directed(BaseGraph<N> graph, N node) {
155+
super(graph, node);
156+
}
157+
158+
@Override
159+
public UnmodifiableIterator<EndpointPair<N>> iterator() {
160+
return Iterators.unmodifiableIterator(
161+
Iterators.concat(
162+
Iterators.transform(
163+
graph.predecessors(node).iterator(),
164+
new Function<N, EndpointPair<N>>() {
165+
@Override
166+
public EndpointPair<N> apply(N predecessor) {
167+
return EndpointPair.ordered(predecessor, node);
168+
}
169+
}),
170+
Iterators.transform(
171+
// filter out 'node' from successors (already covered by predecessors, above)
172+
Sets.difference(graph.successors(node), ImmutableSet.of(node)).iterator(),
173+
new Function<N, EndpointPair<N>>() {
174+
@Override
175+
public EndpointPair<N> apply(N successor) {
176+
return EndpointPair.ordered(node, successor);
177+
}
178+
})));
179+
}
180+
181+
@Override
182+
public int size() {
183+
return graph.inDegree(node)
184+
+ graph.outDegree(node)
185+
- (graph.successors(node).contains(node) ? 1 : 0);
186+
}
187+
188+
@Override
189+
public boolean contains(@NullableDecl Object obj) {
190+
if (!(obj instanceof EndpointPair)) {
191+
return false;
192+
}
193+
194+
EndpointPair<?> endpointPair = (EndpointPair<?>) obj;
195+
if (!endpointPair.isOrdered()) {
196+
return false;
197+
}
198+
199+
Object source = endpointPair.source();
200+
Object target = endpointPair.target();
201+
return (node.equals(source) && graph.successors(node).contains(target))
202+
|| (node.equals(target) && graph.predecessors(node).contains(source));
203+
}
204+
}
205+
206+
private static final class Undirected<N> extends IncidentEdgeSet<N> {
207+
private Undirected(BaseGraph<N> graph, N node) {
208+
super(graph, node);
209+
}
210+
211+
@Override
212+
public UnmodifiableIterator<EndpointPair<N>> iterator() {
213+
return Iterators.unmodifiableIterator(
214+
Iterators.transform(
215+
graph.adjacentNodes(node).iterator(),
216+
new Function<N, EndpointPair<N>>() {
217+
@Override
218+
public EndpointPair<N> apply(N adjacentNode) {
219+
return EndpointPair.unordered(node, adjacentNode);
220+
}
221+
}));
222+
}
223+
224+
@Override
225+
public int size() {
226+
return graph.adjacentNodes(node).size();
227+
}
228+
229+
@Override
230+
public boolean contains(@NullableDecl Object obj) {
231+
if (!(obj instanceof EndpointPair)) {
232+
return false;
233+
}
234+
235+
EndpointPair<?> endpointPair = (EndpointPair<?>) obj;
236+
if (endpointPair.isOrdered()) {
237+
return false;
238+
}
239+
Set<N> adjacent = graph.adjacentNodes(node);
240+
Object nodeU = endpointPair.nodeU();
241+
Object nodeV = endpointPair.nodeV();
242+
243+
return (node.equals(nodeV) && adjacent.contains(nodeU))
244+
|| (node.equals(nodeU) && adjacent.contains(nodeV));
245+
}
246+
}
247+
}
116248
}

android/guava/src/com/google/common/graph/BaseGraph.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,21 @@ interface BaseGraph<N> extends SuccessorsFunction<N>, PredecessorsFunction<N> {
9292
@Override
9393
Set<N> successors(N node);
9494

95+
/**
96+
* Returns the edges in this graph whose endpoints include {@code node}.
97+
*
98+
* @throws IllegalArgumentException if {@code node} is not an element of this graph
99+
*/
100+
Set<EndpointPair<N>> incidentEdges(N node);
101+
95102
/**
96103
* Returns the count of {@code node}'s incident edges, counting self-loops twice (equivalently,
97104
* the number of times an edge touches {@code node}).
98105
*
99106
* <p>For directed graphs, this is equal to {@code inDegree(node) + outDegree(node)}.
100107
*
101-
* <p>For undirected graphs, this is equal to {@code adjacentNodes(node).size()} + (1 if {@code
102-
* node} has an incident self-loop, 0 otherwise).
108+
* <p>For undirected graphs, this is equal to {@code incidentEdges(node).size()} + (number of
109+
* self-loops incident to {@code node}).
103110
*
104111
* <p>If the count is greater than {@code Integer.MAX_VALUE}, returns {@code Integer.MAX_VALUE}.
105112
*

android/guava/src/com/google/common/graph/Graph.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ public interface Graph<N> extends BaseGraph<N> {
145145
@Override
146146
Set<N> successors(N node);
147147

148+
/** {@inheritDoc} */
149+
@Override
150+
Set<EndpointPair<N>> incidentEdges(N node);
151+
148152
/** {@inheritDoc} */
149153
@Override
150154
int degree(N node);

0 commit comments

Comments
 (0)