All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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;
      }
    };
  }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy