Produce edges in a breadth-first-search starting at source, but only return
    nodes that satisfiy a condition, and only iterate past a node if it
    satisfies a different condition.

    conditions are callables that take (G, child, edge) and return true or false

    CommandLine:
        python -m utool.util_graph bfs_conditional

    Example:
        >>> # DISABLE_DOCTEST
        >>> import networkx as nx
        >>> import utool as ut
        >>> G = nx.Graph()
        >>> G.add_edges_from([(1, 2), (1, 3), (2, 3), (2, 4)])
        >>> continue_if = lambda G, child, edge: True
        >>> result = list(ut.bfs_conditional(G, 1, yield_nodes=False))
        >>> print(result)
        [(1, 2), (1, 3), (2, 1), (2, 3), (2, 4), (3, 1), (3, 2), (4, 2)]

    Example:
        >>> # ENABLE_DOCTEST
        >>> import networkx as nx
        >>> import utool as ut
        >>> G = nx.Graph()
        >>> continue_if = lambda G, child, edge: (child % 2 == 0)
        >>> yield_if = lambda G, child, edge: (child % 2 == 1)
        >>> G.add_edges_from([(0, 1), (1, 3), (3, 5), (5, 10),
        >>>                   (4, 3), (3, 6),
        >>>                   (0, 2), (2, 4), (4, 6), (6, 10)])
        >>> result = list(ut.bfs_conditional(G, 0, continue_if=continue_if,
        >>>                                  yield_if=yield_if))
        >>> print(result)
        [1, 3, 5]

def bfs_conditional(G, source, reverse=False, keys=True, data=False,
                    yield_nodes=True, yield_if=None,
                    continue_if=None, visited_nodes=None,
                    yield_source=False):
    """
    Produce edges in a breadth-first-search starting at source, but only return
    nodes that satisfiy a condition, and only iterate past a node if it
    satisfies a different condition.

    conditions are callables that take (G, child, edge) and return true or false

    CommandLine:
        python -m utool.util_graph bfs_conditional

    Example:
        >>> # DISABLE_DOCTEST
        >>> import networkx as nx
        >>> import utool as ut
        >>> G = nx.Graph()
        >>> G.add_edges_from([(1, 2), (1, 3), (2, 3), (2, 4)])
        >>> continue_if = lambda G, child, edge: True
        >>> result = list(ut.bfs_conditional(G, 1, yield_nodes=False))
        >>> print(result)
        [(1, 2), (1, 3), (2, 1), (2, 3), (2, 4), (3, 1), (3, 2), (4, 2)]

    Example:
        >>> # ENABLE_DOCTEST
        >>> import networkx as nx
        >>> import utool as ut
        >>> G = nx.Graph()
        >>> continue_if = lambda G, child, edge: (child % 2 == 0)
        >>> yield_if = lambda G, child, edge: (child % 2 == 1)
        >>> G.add_edges_from([(0, 1), (1, 3), (3, 5), (5, 10),
        >>>                   (4, 3), (3, 6),
        >>>                   (0, 2), (2, 4), (4, 6), (6, 10)])
        >>> result = list(ut.bfs_conditional(G, 0, continue_if=continue_if,
        >>>                                  yield_if=yield_if))
        >>> print(result)
        [1, 3, 5]
    """
    if reverse and hasattr(G, 'reverse'):
        G = G.reverse()
    if isinstance(G, nx.Graph):
        neighbors = functools.partial(G.edges, data=data)
    else:
        neighbors = functools.partial(G.edges, keys=keys, data=data)

    queue = collections.deque([])

    if visited_nodes is None:
        visited_nodes = set([])
    else:
        visited_nodes = set(visited_nodes)

    if source not in visited_nodes:
        if yield_nodes and yield_source:
            yield source
        visited_nodes.add(source)
        new_edges = neighbors(source)
        if isinstance(new_edges, list):
            new_edges = iter(new_edges)
        queue.append((source, new_edges))

    while queue:
        parent, edges = queue[0]
        for edge in edges:
            child = edge[1]
            if yield_nodes:
                if child not in visited_nodes:
                    if yield_if is None or yield_if(G, child, edge):
                        yield child
            else:
                if yield_if is None or yield_if(G, child, edge):
                    yield edge
            if child not in visited_nodes:
                visited_nodes.add(child)
                # Add new children to queue if the condition is satisfied
                if continue_if is None or continue_if(G, child, edge):
                    new_edges = neighbors(child)
                    if isinstance(new_edges, list):
                        new_edges = iter(new_edges)
                    queue.append((child, new_edges))
        queue.popleft()