Works on a single connected component to produce the edge lists of the biconnected components.
    Returns a list of lists, each containing the edges that combine to produce the connected component.
    Returns a single nested list with 1 edge if there is only 1 edge in the graph (a 2-node graph is a
    special case, generally considered to be a biconnected graph).
    Returns an empty list if there are no edges in the graph (i.e. if it's a single-node or empty graph).

def _internal_get_biconnected_components_edge_lists(graph):
    """Works on a single connected component to produce the edge lists of the biconnected components.
    Returns a list of lists, each containing the edges that combine to produce the connected component.
    Returns a single nested list with 1 edge if there is only 1 edge in the graph (a 2-node graph is a
    special case, generally considered to be a biconnected graph).
    Returns an empty list if there are no edges in the graph (i.e. if it's a single-node or empty graph).
    """
    list_of_components = []

    num_nodes = graph.num_nodes()
    num_edges = graph.num_edges()
    if num_nodes <= 2:
        if num_edges == 1:
            list_of_components.append(graph.get_all_edge_ids())
        return list_of_components

    dfs_count = 0
    edge_stack = deque()
    dfs_stack = deque()
    visited = defaultdict(lambda: False)
    parent = defaultdict(lambda: None)
    depth = {}
    low = {}
    preorder_processed = defaultdict(lambda: False)
    postorder_processed = defaultdict(lambda: False)

    # We're simulating a recursive DFS with an explicit stack, since Python has a really small function stack
    unvisited_nodes = set(graph.get_all_node_ids())
    while len(unvisited_nodes) > 0:
        # --Initialize the first stack frame, simulating the DFS call on the root node
        u = unvisited_nodes.pop()
        parent[u] = u
        stack_frame = {
            'u': u,
            'v': None,
            'remaining_children': graph.neighbors(u)
        }
        dfs_stack.appendleft(stack_frame)

        while len(dfs_stack) > 0:
            frame = dfs_stack.popleft()
            u = frame['u']
            v = frame['v']

            if not visited[u]:
                if u in unvisited_nodes:
                    unvisited_nodes.remove(u)
                visited[u] = True
                dfs_count += 1
                depth[u] = dfs_count
                low[u] = depth[u]
                if len(frame['remaining_children']) > 0:
                    v = frame['remaining_children'].pop()
                    frame['v'] = v

            if v is None:
                # --u has no neighbor nodes
                continue

            if not preorder_processed[v]:
                # --This is the preorder processing, done for each neighbor node ''v'' of u
                node_v = graph.get_node(v)
                for edge_id in node_v['edges']:
                    edge = graph.get_edge(edge_id)
                    tpl_a = (u, v)
                    tpl_b = (v, u)
                    if tpl_a == edge['vertices'] or tpl_b == edge['vertices']:
                        edge_stack.appendleft(edge_id)
                        break
                parent[v] = u
                preorder_processed[v] = True
                # print 'preorder for {}'.format(v)
                dfs_stack.appendleft(frame)

                # --Simulate the recursion to call the DFS on v
                new_frame = {
                    'u': v,
                    'v': None,
                    'remaining_children': graph.neighbors(v)
                }
                dfs_stack.appendleft(new_frame)
                continue

            elif not postorder_processed[v] and u == parent[v]:
                # --This is the postorder processing, done for each neighbor node ''v'' of u
                if low[v] >= depth[u]:
                    component = output_component(graph, edge_stack, u, v)
                    if len(component) > 2:
                        # --You can't have a biconnected component with less than 3 edges
                        list_of_components.append(component)
                low[u] = min(low[u], low[v])
                postorder_processed[v] = True
                # print 'postorder for {}'.format(v)

            elif visited[v] and (parent[u] != v) and (depth[v] < depth[u]):
                # (u,v) is a backedge from u to its ancestor v
                node_v = graph.get_node(v)
                for edge_id in node_v['edges']:
                    edge = graph.get_edge(edge_id)
                    tpl_a = (u, v)
                    tpl_b = (v, u)
                    if tpl_a == edge['vertices'] or tpl_b == edge['vertices']:
                        edge_stack.appendleft(edge_id)
                        break
                low[u] = min(low[u], depth[v])

            if len(frame['remaining_children']) > 0:
                # --Continue onto the next neighbor node of u
                v = frame['remaining_children'].pop()
                frame['v'] = v
                dfs_stack.appendleft(frame)

    return list_of_components