"""StrongConnectivity.py

DFS-based algorithm for computing strongly connected components.

D. Eppstein, July 2005.
"""

import unittest
import DFS

try:
    set
except NameError:
    from sets import Set as set

class StronglyConnectedComponents(DFS.Searcher):
    """
    Generate the strongly connected components of G.  G should be
    represented in such a way that "for v in G" loops through the
    vertices, and "G[v]" produces a list of the neighbors of v;
    for instance, G may be a dictionary mapping each vertex to its
    neighbor set.  The result of StronglyConnectedComponents(G) is
    a sequence of subgraphs of G.
    """

    def __init__(self,G):
        """Search for strongly connected components of graph G."""

        # set up data structures for DFS
        self._components = []
        self._dfsnumber = {}
        self._activelen = {}
        self._active = []
        self._low = {}
        self._biglow = len(G)
        self._graph = G

        # perform the Depth First Search
        DFS.Searcher.__init__(self,G)

        # clean up now-useless data structures
        del self._dfsnumber, self._activelen, self._active, self._low

    def __iter__(self):
        """Return iterator for sequence of strongly connected components."""
        return iter(self._components)

    def _component(self,vertices):
        """Make a new SCC."""
        vertices = set(vertices)
        induced = dict([(v,set([w for w in self._graph[v] if w in vertices]))
                        for v in vertices])
        self._components.append(induced)

    def preorder(self,parent,child):
        """Handle first visit to vertex in DFS search for components."""
        if parent == child:
            self._active = []
        self._activelen[child] = len(self._active)
        self._active.append(child)
        self._low[child] = self._dfsnumber[child] = len(self._dfsnumber)

    def backedge(self,source,destination):
        """Handle non-tree edge in DFS search for components."""
        self._low[source] = min(self._low[source],self._low[destination])

    def postorder(self,parent,child):
        """Handle last visit to vertex in DFS search for components."""
        if self._low[child] == self._dfsnumber[child]:
            self._component(self._active[self._activelen[child]:])
            for v in self._components[-1]:
                self._low[v] = self._biglow
            del self._active[self._activelen[child]:]
        else:
            self._low[parent] = min(self._low[parent],self._low[child])

# If run as "python StrongConnectivity.py", run tests on various small graphs
# and check that the correct results are obtained.

class StrongConnectivityTest(unittest.TestCase):
    G1 = { 0:[1], 1:[2,3], 2:[4,5], 3:[4,5], 4:[6], 5:[], 6:[] }
    C1 = [[0],[1],[2],[3],[4],[5],[6]]

    G2 = { 0:[1], 1:[2,3,4], 2:[0,3], 3:[4], 4:[3] }
    C2 = [[0,1,2],[3,4]]

    knownpairs = [(G1,C1),(G2,C2)]

    def testStronglyConnectedComponents(self):
        """Check known graph/component pairs."""
        for (graph,expectedoutput) in self.knownpairs:
            output = [list(C) for C in StronglyConnectedComponents(graph)]
            for component in output:
                component.sort()
            output.sort()
            self.assertEqual(output,expectedoutput)

    def testSubgraph(self):
        """Check that each SCC is an induced subgraph."""
        for (graph,expectedoutput) in self.knownpairs:
            components = StronglyConnectedComponents(graph)
            for C in components:
                for v in C:
                    for w in graph:
                        self.assertEqual(w in graph[v] and w in C, w in C[v])

if __name__ == "__main__":
    unittest.main()
