Skip to content

Commit

Permalink
Make incidentEdges() return edges in insertion order
Browse files Browse the repository at this point in the history
RELNOTES=n/a

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=282777947
  • Loading branch information
nymanjens authored and nick-someone committed Dec 1, 2019
1 parent 032201f commit df2c552
Show file tree
Hide file tree
Showing 20 changed files with 484 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,17 +73,17 @@ public void successors_returnsInConnectingEdgeInsertionOrder() {
// Note: Stable order means that the ordering doesn't change between iterations and versions.
// Ideally, the ordering in test should never be updated.
@Test
public void incidentEdges_returnsInStableOrder() {
public void incidentEdges_returnsInEdgeInsertionOrder() {
populateStarShapedGraph(graph);

assertThat(graph.incidentEdges(1))
.containsExactly(
EndpointPair.ordered(2, 1),
EndpointPair.ordered(5, 1),
EndpointPair.ordered(3, 1),
EndpointPair.ordered(1, 4),
EndpointPair.ordered(1, 3),
EndpointPair.ordered(1, 2))
EndpointPair.ordered(5, 1),
EndpointPair.ordered(1, 2),
EndpointPair.ordered(3, 1))
.inOrder();
}

Expand Down
152 changes: 36 additions & 116 deletions android/guava/src/com/google/common/graph/AbstractBaseGraph.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,42 @@ && nodes().contains(endpointPair.nodeU())
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);
return new IncidentEdgeSet<N>(this, node) {
@Override
public UnmodifiableIterator<EndpointPair<N>> iterator() {
if (graph.isDirected()) {
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);
}
})));
} else {
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
Expand Down Expand Up @@ -155,119 +190,4 @@ protected final void validateEndpoints(EndpointPair<?> endpoints) {
protected final boolean isOrderingCompatible(EndpointPair<?> endpoints) {
return endpoints.isOrdered() || !this.isDirected();
}

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));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static com.google.common.graph.GraphConstants.DEFAULT_NODE_COUNT;
import static com.google.common.graph.Graphs.checkNonNegative;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
Expand Down Expand Up @@ -114,6 +115,18 @@ public Set<N> successors(N node) {
return checkedConnections(node).successors();
}

@Override
public Set<EndpointPair<N>> incidentEdges(N node) {
final GraphConnections<N, V> connections = checkedConnections(node);

return new IncidentEdgeSet<N>(this, node) {
@Override
public Iterator<EndpointPair<N>> iterator() {
return connections.incidentEdgeIterator(node);
}
};
}

@Override
public boolean hasEdgeConnecting(N nodeU, N nodeV) {
return hasEdgeConnecting_internal(checkNotNull(nodeU), checkNotNull(nodeV));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
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.Iterators;
import com.google.common.collect.UnmodifiableIterator;
import java.util.AbstractSet;
import java.util.ArrayList;
Expand All @@ -36,6 +38,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

/**
Expand Down Expand Up @@ -323,6 +326,63 @@ public boolean contains(@NullableDecl Object obj) {
};
}

@Override
public Iterator<EndpointPair<N>> incidentEdgeIterator(final N 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public static <S> ElementOrder<S> unordered() {
* <li>{@code adjacentNodes(node)}: Connecting edge insertion order
* <li>{@code predecessors(node)}: Connecting edge insertion order
* <li>{@code successors(node)}: Connecting edge insertion order
* <li>{@code incidentEdges(node)}: Stable order
* <li>{@code incidentEdges(node)}: Edge insertion order
* </ul>
* <li>For {@link Network}:
* <ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ public Set<N> successors(N node) {
return delegate().successors(node);
}

@Override
public Set<EndpointPair<N>> incidentEdges(N node) {
return delegate().incidentEdges(node);
}

@Override
public int degree(N node) {
return delegate().degree(node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.google.common.graph;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Iterator;
import java.util.Set;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

Expand All @@ -36,6 +37,13 @@ interface GraphConnections<N, V> {

Set<N> successors();

/**
* Returns an iterator over the incident edges.
*
* @param thisNode The node that this all of the connections in this class are connected to.
*/
Iterator<EndpointPair<N>> incidentEdgeIterator(N thisNode);

/**
* Returns the value associated with the edge connecting the origin node to {@code node}, or null
* if there is no such edge.
Expand Down
20 changes: 20 additions & 0 deletions android/guava/src/com/google/common/graph/Graphs.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
import static com.google.common.graph.GraphConstants.NODE_NOT_IN_GRAPH;

import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
Expand Down Expand Up @@ -276,6 +279,23 @@ public Set<N> successors(N node) {
return delegate().predecessors(node); // transpose
}

@Override
public Set<EndpointPair<N>> incidentEdges(N node) {
return new IncidentEdgeSet<N>(this, node) {
@Override
public Iterator<EndpointPair<N>> iterator() {
return Iterators.transform(
delegate().incidentEdges(node).iterator(),
new Function<EndpointPair<N>, EndpointPair<N>>() {
@Override
public EndpointPair<N> apply(EndpointPair<N> edge) {
return EndpointPair.of(delegate(), edge.nodeV(), edge.nodeU());
}
});
}
};
}

@Override
public int inDegree(N node) {
return delegate().outDegree(node); // transpose
Expand Down

0 comments on commit df2c552

Please sign in to comment.