require "set" require "tsort" module Librarian module Algorithms class GraphHash < Hash include TSort def tsort_each_node(&block) keys.sort.each(&block) # demand determinism end def tsort_each_child(node, &block) children = self[node] children && children.sort.each(&block) # demand determinism end class << self def from(hash) o = new hash.each{|k, v| o[k] = v} o end end end module AdjacencyListDirectedGraph extend self def cyclic?(graph) each_cyclic_strongly_connected_component_set(graph).any? end # Topological sort of the graph with an approximately minimal feedback arc # set removed. def tsort_cyclic(graph) fag = feedback_arc_graph(graph) reduced_graph = subtract_edges_graph(graph, fag) GraphHash.from(reduced_graph).tsort end # Returns an approximately minimal feedback arc set, lifted into a graph. def feedback_arc_graph(graph) edges_to_graph(feedback_arc_set(graph)) end # Returns an approximately minimal feedback arc set. def feedback_arc_set(graph) fas = feedback_arc_set_step0(graph) feedback_arc_set_step1(graph, fas) end private def edges_to_graph(edges) graph = {} edges.each do |(u, v)| graph[u] ||= [] graph[u] << v graph[v] ||= nil end graph end def subtract_edges_graph(graph, edges_graph) xgraph = {} graph.each do |n, vs| dests = edges_graph[n] xgraph[n] = !vs ? vs : !dests ? vs : vs - dests end xgraph end def each_cyclic_strongly_connected_component_set(graph) return enum_for(__method__, graph) unless block_given? GraphHash.from(graph).each_strongly_connected_component do |scc| if scc.size == 1 n = scc.first vs = graph[n] or next vs.include?(n) or next end yield scc end end # Partitions the graph into its strongly connected component subgraphs, # removes the acyclic single-vertex components (multi-vertex components # are guaranteed to be cyclic), and yields each cyclic strongly connected # component. def each_cyclic_strongly_connected_component_graph(graph) return enum_for(__method__, graph) unless block_given? each_cyclic_strongly_connected_component_set(graph) do |scc| sccs = scc.to_set sccg = GraphHash.new scc.each do |n| vs = graph[n] sccg[n] = vs && vs.select{|v| sccs.include?(v)} end yield sccg end end # The 0th step of computing a feedback arc set: pick out vertices whose # removals will make the graph acyclic. def feedback_arc_set_step0(graph) fas = [] each_cyclic_strongly_connected_component_graph(graph) do |scc| scc.keys.sort.each do |n| # demand determinism vs = scc[n] or next vs.size > 0 or next vs.sort! # demand determinism fas << [n, vs.shift] break end end fas end # The 1st step of computing a feedback arc set: pick out vertices from the # 0th step whose removals turn out to be unnecessary. def feedback_arc_set_step1(graph, fas) reduced_graph = subtract_edges_graph(graph, edges_to_graph(fas)) fas.select do |(u, v)| reduced_graph[u] ||= [] reduced_graph[u] << v cyclic = cyclic?(reduced_graph) reduced_graph[u].pop if cyclic cyclic end end end end end