package com.hubspot.blazar.data.util; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.SetMultimap; import com.google.common.collect.TreeMultimap; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; public enum GraphUtils { INSTANCE; public <V extends Comparable<V>> List<V> topologicalSort(SetMultimap<V, V> transitiveReduction) { SetMultimap<V, V> graph = TreeMultimap.create(transitiveReduction); List<V> sorted = new ArrayList<>(); Deque<V> roots = new ArrayDeque<>(); for (V vertex : graph.keySet()) { if (incomingVertices(graph, vertex).isEmpty()) { roots.add(vertex); } } while (!roots.isEmpty()) { V root = roots.removeFirst(); sorted.add(root); Set<V> children = graph.removeAll(root); for (V child : children) { if (incomingVertices(graph, child).isEmpty()) { roots.addLast(child); } } } return sorted; } // working from the bottom of the graph should be a lot faster public <V> SetMultimap<V, V> retain(SetMultimap<V, V> graph, Set<V> vertices) { SetMultimap<V, V> reduced = HashMultimap.create(); SetMultimap<V, V> inverted = HashMultimap.create(); Deque<Edge<V>> queue = new ArrayDeque<>(); Set<Edge<V>> processed = new HashSet<>(); for (Entry<V, V> entry : graph.entries()) { inverted.put(entry.getValue(), entry.getKey()); if (vertices.contains(entry.getValue())) { queue.add(new Edge<>(entry.getKey(), entry.getValue())); } } while (!queue.isEmpty()) { Edge<V> edge = queue.removeFirst(); processed.add(edge); if (vertices.contains(edge.getSource())) { reduced.put(edge.getSource(), edge.getTarget()); } else { for (V parent : inverted.get(edge.getSource())) { Edge<V> newEdge = new Edge<>(parent, edge.getTarget()); if (!processed.contains(newEdge)) { queue.addLast(newEdge); } } } } return reduced; } public <V> SetMultimap<V, V> transitiveReduction(SetMultimap<V, V> edges) { SetMultimap<V, V> paths = findAllPaths(edges); SetMultimap<V, V> reduced = HashMultimap.create(paths); Set<V> vertices = vertices(paths); for (V vertexJ : vertices) { for (V vertexI : vertices) { if (reduced.get(vertexI).contains(vertexJ)) { for (V vertexK : vertices) { if (reduced.get(vertexJ).contains(vertexK)) { reduced.get(vertexI).remove(vertexK); } } } } } return reduced; } private <V> Set<V> incomingVertices(SetMultimap<V, V> graph, V target) { Set<V> incomingVertices = new HashSet<>(); for (Entry<V, V> path : graph.entries()) { if (path.getValue().equals(target)) { incomingVertices.add(path.getKey()); } } return incomingVertices; } private <V> SetMultimap<V, V> findAllPaths(SetMultimap<V, V> edges) { SetMultimap<V, V> paths = HashMultimap.create(edges); Set<V> vertices = vertices(paths); for (V vertexI : vertices) { for (V vertexJ : vertices) { if (paths.get(vertexJ).contains(vertexI)) { for (V vertexK : vertices) { if (paths.get(vertexI).contains(vertexK)) { paths.get(vertexJ).add(vertexK); } } } } } return paths; } private <V> Set<V> vertices(SetMultimap<V, V> graph) { return ImmutableSet.<V>builder().addAll(graph.keySet()).addAll(graph.values()).build(); } private static class Edge<T> { private final T source; private final T target; public Edge(T source, T target) { this.source = source; this.target = target; } public T getSource() { return source; } public T getTarget() { return target; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Edge edge = (Edge) o; return Objects.equals(source, edge.source) && Objects.equals(target, edge.target); } @Override public int hashCode() { return Objects.hash(source, target); } } }