# connected_components.rb
#
# This file contains the algorithms for the connected components of an
# undirected graph (each_connected_component) and strongly connected components
# for directed graphs (strongly_connected_components).
#
require 'laser/third_party/rgl/traversal'

module RGL

  module Graph

    # Compute the connected components of an undirected graph, using a
    # DFS (Depth-first search)-based approach.  A _connected component_ of
    # an undirected graph is a set of vertices that are all reachable
    # from each other.
    #
    # The function is implemented as an iterator which calls the client
    # with an array of vertices for each component.
    #
    # It raises an exception if the graph is directed.

    def each_connected_component
      raise NotUndirectedError,
        "each_connected_component only works " +
        "for undirected graphs." if directed?
      comp = []
      vis  = DFSVisitor.new(self)
      vis.set_finish_vertex_event_handler { |v| comp << v }
      vis.set_start_vertex_event_handler { |v|
        yield comp unless comp.empty?
        comp = []
      }
      depth_first_search(vis) { |v| }
      yield comp unless comp.empty?
    end

    # This GraphVisitor is used by strongly_connected_components to compute
    # the strongly connected components of a directed graph.

    class TarjanSccVisitor < DFSVisitor

      attr_reader :comp_map

      # Creates a new TarjanSccVisitor for graph _g_, which should be directed.

      def initialize (g)
        super g
        @root_map           = {}
        @comp_map           = {}
        @discover_time_map  = {}
        @dfs_time           = 0
        @c_index            = 0
        @stack              = []
      end

      def handle_examine_vertex (v)
        @root_map[v] =  v
        @comp_map[v] =  -1
        @dfs_time    += 1
        @discover_time_map[v] = @dfs_time
        @stack.push(v)
      end

      def handle_finish_vertex (v)
        # Search adjacent vertex w with earliest discover time
        root_v = @root_map[v]
        graph.each_adjacent(v) do |w|
          if @comp_map[w] == -1
            root_v = min_discover_time(root_v, @root_map[w])
          end
        end
        @root_map[v] = root_v
        if root_v == v                  # v is topmost vertex of a SCC
          begin                         # pop off all vertices until v
            w = @stack.pop
            @comp_map[w] = @c_index
          end until w == v
          @c_index += 1
        end
      end

      # Return the number of components found so far.

      def num_comp
        @c_index
      end

      private

      def min_discover_time (u, v)
        @discover_time_map[u] < @discover_time_map[v] ? u : v
      end

    end		# class TarjanSccVisitor

    # This is Tarjan's algorithm for strongly connected components, from his
    # paper "Depth first search and linear graph algorithms".  It calculates
    # the components in a single application of DFS.  We implement the
    # algorithm with the help of the DFSVisitor TarjanSccVisitor.
    #
    # === Definition
    # 
    # A _strongly connected component_ of a directed graph G=(V,E) is a
    # maximal set of vertices U which is in V, such that for every pair of
    # vertices u and  v in U, we have both a path from u to v and a path
    # from v to u. That is to say, u and v are reachable from each other.
    # 
    # @Article{Tarjan:1972:DFS,
    #   author =       "R. E. Tarjan",
    #   key =          "Tarjan",
    #   title =        "Depth First Search and Linear Graph Algorithms",
    #   journal =      "SIAM Journal on Computing",
    #   volume =       "1",
    #   number =       "2",
    #   pages =        "146--160",
    #   month =        jun,
    #   year =         "1972",
    #   CODEN =        "SMJCAT",
    #   ISSN =         "0097-5397 (print), 1095-7111 (electronic)",
    #   bibdate =      "Thu Jan 23 09:56:44 1997",
    #   bibsource =    "Parallel/Multi.bib, Misc/Reverse.eng.bib",
    # }
    # 
    # The output of the algorithm is recorded in a TarjanSccVisitor _vis_.
    # vis.comp_map will contain numbers giving the component ID assigned to
    # each vertex. The number of components is vis.num_comp.

    def strongly_connected_components
      raise NotDirectedError,
        "strong_components only works for directed graphs." unless directed?
      vis = TarjanSccVisitor.new(self)
      depth_first_search(vis) { |v| }
      vis
    end

  end                           # module Graph
end                             # module RGL
