Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.lacuna.bifurcan.Graphs Maven / Gradle / Ivy
package io.lacuna.bifurcan;
import io.lacuna.bifurcan.utils.Iterators;
import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import java.util.stream.Collectors;
import static java.lang.Math.max;
import static java.lang.Math.min;
/**
* @author ztellman
*/
public class Graphs {
static final BinaryOperator MERGE_LAST_WRITE_WINS = (a, b) -> b;
public static class Edge implements IEdge {
public final E value;
public final V from, to;
private int hash = -1;
public Edge(E value, V from, V to) {
this.value = value;
this.from = from;
this.to = to;
}
public static Edge create(IGraph graph, V from, V to) {
return new Edge<>(graph.edge(from, to), from, to);
}
@Override
public V from() {
return from;
}
@Override
public V to() {
return to;
}
@Override
public E value() {
return value;
}
@Override
public int hashCode() {
if (hash == -1) {
hash = from.hashCode() ^ to.hashCode() ^ value.hashCode();
}
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (obj instanceof Edge) {
Edge e = (Edge) obj;
return Objects.equals(from, e.from) && Objects.equals(to, e.to) && Objects.equals(value, e.value);
}
return false;
}
}
/// utilities
public static boolean equals(IGraph a, IGraph b) {
if (a.isDirected() != b.isDirected() || !a.vertices().equals(b.vertices())) {
return false;
}
for (V v : a.vertices()) {
ISet aOut = a.out(v);
ISet bOut = b.out(v);
if (!aOut.equals(bOut)) {
return false;
}
for (V w : aOut) {
if (!Objects.equals(a.edge(v, w), b.edge(v, w))) {
return false;
}
}
}
return true;
}
public static int hash(IGraph g) {
int hash = g.vertices().stream().mapToInt(Objects::hashCode).reduce(0, (a, b) -> a ^ b);
if (g.isDirected()) {
for (V v : g.vertices()) {
for (V w : g.out(v)) {
hash = hash ^ (Objects.hashCode(w) * 31) ^ Objects.hashCode(g.edge(v, w));
}
}
} else {
for (V v : g.vertices()) {
for (V w : g.out(v)) {
hash = hash ^ Objects.hashCode(v) ^ Objects.hashCode(w) ^ Objects.hashCode(g.edge(v, w));
}
}
}
return hash;
}
public static IGraph merge(IGraph a, IGraph b, BinaryOperator merge) {
if (a.isDirected() != b.isDirected()) {
throw new IllegalArgumentException("cannot merge directed and undirected graphs");
}
if (a.vertices().size() < b.vertices().size()) {
return merge(b, a, (x, y) -> merge.apply(y, x));
}
IGraph result = a.forked().linear();
for (V src : b.vertices()) {
for (V dst : b.out(src)) {
a = a.link(src, dst, b.edge(src, dst), merge);
}
}
return result.forked();
}
/// search
private static class ShortestPathState {
public final V origin, node;
public final ShortestPathState prev;
public final double distance;
private ShortestPathState(V origin) {
this.origin = origin;
this.prev = null;
this.node = origin;
this.distance = 0;
}
public ShortestPathState(V node, ShortestPathState prev, double edge) {
this.origin = prev.origin;
this.node = node;
this.prev = prev;
this.distance = prev.distance + edge;
}
public IList path() {
IList result = new LinearList<>();
ShortestPathState curr = this;
for (; ; ) {
result.addFirst(curr.node);
if (curr.node.equals(curr.origin)) {
break;
}
curr = curr.prev;
}
return result;
}
}
public static Optional> shortestPath(IGraph graph, V from, Predicate accept, ToDoubleFunction> cost) {
return shortestPath(graph, LinearList.of(from), accept, cost);
}
/**
* @return the shortest path, if one exists, between a starting vertex and an accepted vertex, excluding trivial
* solutions where a starting vertex is accepted.
*/
public static Optional> shortestPath(IGraph graph, Iterable start, Predicate accept, ToDoubleFunction> cost) {
IMap>> originStates = new LinearMap<>();
PriorityQueue> queue = new PriorityQueue<>(Comparator.comparingDouble(x -> x.distance));
for (V v : start) {
if (graph.vertices().contains(v)) {
ShortestPathState init = new ShortestPathState<>(v);
originStates.getOrCreate(v, LinearMap::new).put(v, init);
queue.add(init);
}
}
ShortestPathState curr;
for (; ; ) {
curr = queue.poll();
if (curr == null) {
return Optional.empty();
}
IMap> states = originStates.get(curr.origin).get();
if (states.get(curr.node).get() != curr) {
continue;
} else if (curr.prev != null && accept.test(curr.node)) {
return Optional.of(List.from(curr.path()));
}
for (V v : graph.out(curr.node)) {
double edge = cost.applyAsDouble(new Edge(graph.edge(curr.node, v), curr.node, v));
if (edge < 0) {
throw new IllegalArgumentException("negative edge weights are unsupported");
}
ShortestPathState next = states.get(v, null);
if (next == null) {
next = new ShortestPathState(v, curr, edge);
} else if (curr.distance + edge < next.distance) {
next = new ShortestPathState(v, curr, edge);
} else {
continue;
}
states.put(v, next);
queue.add(next);
}
}
}
/// undirected graphs
public static Set> connectedComponents(IGraph graph) {
if (graph.isDirected()) {
throw new IllegalArgumentException("graph must be undirected, try Graphs.stronglyConnectedComponents instead");
}
LinearSet traversed = new LinearSet<>((int) graph.vertices().size(), graph.vertexHash(), graph.vertexEquality());
Set> result = new Set>().linear();
for (V seed : graph.vertices()) {
if (!traversed.contains(seed)) {
traversed.add(seed);
Set group = new Set<>(graph.vertexHash(), graph.vertexEquality()).linear();
bfsVertices(LinearList.of(seed), graph::out).forEachRemaining(group::add);
result.add(group.forked());
}
}
return result.forked();
}
public static Set> biconnectedComponents(IGraph graph) {
Set cuts = articulationPoints(graph);
Set> result = new Set>().linear();
for (Set component : connectedComponents(graph.select(graph.vertices().difference(cuts)))) {
result.add(
component.union(
cuts.stream()
.filter(v -> graph.out(v).containsAny(component))
.collect(Sets.collector())));
}
for (int i = 0; i < cuts.size() - 1; i++) {
for (int j = i + 1; j < cuts.size(); j++) {
V a = cuts.nth(i);
V b = cuts.nth(i + 1);
if (graph.out(a).contains(b)) {
result.add(Set.of(a, b));
}
}
}
return result.forked();
}
private static class ArticulationPointState {
final V node;
final int depth;
int lowlink;
int childCount = 0;
public ArticulationPointState(V node, int depth) {
this.node = node;
this.depth = depth;
this.lowlink = depth;
}
}
public static Set articulationPoints(IGraph graph) {
if (graph.isDirected()) {
throw new IllegalArgumentException("graph must be undirected");
}
// algorithmic state
IMap> state = new LinearMap<>(
(int) graph.vertices().size(),
graph.vertexHash(),
graph.vertexEquality());
// call-stack state
LinearList> path = new LinearList<>();
LinearList> branches = new LinearList<>();
Set result = new Set().linear();
for (V seed : graph.vertices()) {
if (state.contains(seed)) {
continue;
}
ArticulationPointState s = new ArticulationPointState<>(seed, 0);
path.addLast(s);
branches.addLast(graph.out(seed).iterator());
state.put(seed, s);
while (path.size() > 0) {
// traverse deeper
if (branches.last().hasNext()) {
V w = branches.last().next();
ArticulationPointState vs = path.last();
ArticulationPointState ws = state.get(w, null);
if (ws == null) {
ws = new ArticulationPointState<>(w, (int) path.size());
vs.childCount++;
state.put(w, ws);
path.addLast(ws);
branches.addLast(graph.out(w).iterator());
} else {
vs.lowlink = min(vs.lowlink, ws.depth);
}
// return
} else {
branches.popLast();
ArticulationPointState ws = path.popLast();
if (path.size() > 0) {
ArticulationPointState vs = path.last();
vs.lowlink = min(ws.lowlink, vs.lowlink);
if ((path.size() > 1 && ws.lowlink >= vs.depth)
|| (path.size() == 1 && vs.childCount > 1)) {
result.add(vs.node);
}
}
}
}
}
return result.forked();
}
/// directed graphs
private static class TarjanState {
final int index;
int lowlink;
boolean onStack;
public TarjanState(int index) {
this.index = index;
this.lowlink = index;
this.onStack = true;
}
}
/**
* @param graph a directed graph
* @param includeSingletons if false, omits any singleton vertex sets
* @return a set of all strongly connected vertices in the graph
*/
public static Set> stronglyConnectedComponents(IGraph graph, boolean includeSingletons) {
if (!graph.isDirected()) {
throw new IllegalArgumentException("graph must be directed, try Graphs.connectedComponents instead");
}
// algorithmic state
IMap state = new LinearMap<>(
(int) graph.vertices().size(),
graph.vertexHash(),
graph.vertexEquality());
LinearList stack = new LinearList<>();
// call-stack state
LinearList path = new LinearList<>();
LinearList> branches = new LinearList<>();
Set> result = new Set>().linear();
for (V seed : graph.vertices()) {
if (state.contains(seed)) {
continue;
}
branches.addLast(LinearList.of(seed).iterator());
do {
// traverse deeper
if (branches.last().hasNext()) {
V w = branches.last().next();
TarjanState ws = state.get(w, null);
if (ws == null) {
ws = new TarjanState((int) state.size());
state.put(w, ws);
stack.addLast(w);
path.addLast(w);
branches.addLast(graph.out(w).iterator());
} else if (ws.onStack) {
TarjanState vs = state.get(path.last()).get();
vs.lowlink = min(vs.lowlink, ws.index);
}
// return
} else {
branches.popLast();
V w = path.popLast();
TarjanState ws = state.get(w).get();
// update predecessor's lowlink, if they exist
if (path.size() > 0) {
V v = path.last();
TarjanState vs = state.get(v).get();
vs.lowlink = min(vs.lowlink, ws.lowlink);
}
// create a new group
if (ws.lowlink == ws.index) {
if (!includeSingletons && stack.last() == w) {
stack.popLast();
state.get(w).get().onStack = false;
} else {
Set group = new Set(graph.vertexHash(), graph.vertexEquality()).linear();
for (; ; ) {
V x = stack.popLast();
group.add(x);
state.get(x).get().onStack = false;
if (x == w) {
break;
}
}
result.add(group.forked());
}
}
}
} while (path.size() > 0);
}
return result.forked();
}
public static List> stronglyConnectedSubgraphs(IGraph graph, boolean includeSingletons) {
List> result = new List>().linear();
stronglyConnectedComponents(graph, includeSingletons).forEach(s -> result.addLast(graph.select(s)));
return result.forked();
}
public static List> cycles(IGraph graph) {
if (!graph.isDirected()) {
throw new IllegalArgumentException("graph must be directed");
}
// traversal
LinearList path = new LinearList<>();
IList> branches = new LinearList<>();
//state
LinearSet blocked = new LinearSet<>(graph.vertexHash(), graph.vertexEquality());
LinearMap> blocking = new LinearMap<>();
List> result = new List>().linear();
for (IGraph subgraph : stronglyConnectedSubgraphs(graph, true)) {
// simple rings are a pathological input for this algorithm, and also very common
if (subgraph.vertices().stream().allMatch(v -> subgraph.out(v).size() == 1)) {
V seed = subgraph.vertices().nth(0);
result.addLast(List.from(bfsVertices(seed, subgraph::out)).addLast(seed));
continue;
}
for (V seed : subgraph.vertices()) {
long threshold = subgraph.indexOf(seed);
path.addLast(seed);
branches.addLast(subgraph.out(seed).iterator());
blocked.clear();
blocking.clear();
int depth = 1;
do {
// traverse deeper
if (branches.last().hasNext()) {
V v = branches.last().next();
if (subgraph.indexOf(v) < threshold) {
continue;
}
if (subgraph.vertexEquality().test(seed, v)) {
result.addLast(List.from(path).addLast(seed));
depth = 0;
} else if (!blocked.contains(v)) {
path.addLast(v);
depth++;
branches.addLast(subgraph.out(v).iterator());
}
blocked.add(v);
// return
} else {
V v = path.popLast();
depth = max(-1, depth - 1);
if (depth < 0) {
LinearList stack = new LinearList().addFirst(v);
while (stack.size() > 0) {
V u = stack.popLast();
if (blocked.contains(u)) {
blocked.remove(u);
blocking.get(u, (ISet) Sets.EMPTY).forEach(stack::addLast);
blocking.remove(u);
}
}
} else {
graph.out(v).forEach(u -> blocking.getOrCreate(u, LinearSet::new).add(v));
}
branches.removeLast();
}
} while (path.size() > 0);
}
}
return result.forked();
}
/// traversal
public static Iterator bfsVertices(V start, Function> adjacent) {
return bfsVertices(LinearList.of(start), adjacent);
}
public static Iterator bfsVertices(Iterable start, Function> adjacent) {
LinearList queue = new LinearList<>();
ISet traversed = new LinearSet<>();
start.forEach(queue::addLast);
return new Iterator() {
@Override
public boolean hasNext() {
return queue.size() > 0;
}
@Override
public V next() {
V v = queue.popFirst();
traversed.add(v);
adjacent.apply(v).forEach(w -> {
if (!traversed.contains(w)) {
queue.addLast(w);
}
});
return v;
}
};
}
}