Source:
<code>public static <T> Digraph<T> buildFilteredDigraph(Digraph<T> initialGraph, Predicate<T> filterOut, Class<T> clazz) { Digraph<T> res = new Digraph<>(clazz); Set<T> toRemove = new LinkedHashSet<>(); for (Vertex<T> vertex : initialGraph.vertexList) { T id = vertex.id; if (!filterOut.test(id)) { res.addVertex(id); } else { toRemove.add(id); } } if (res.vertexList.size() < 2) { return res; } final Set<Edge<T>> edgesWithRemovedRef = new LinkedHashSet<>(); initialGraph.getEdges().forEach(edge -> { if (toRemove.contains(edge.from) || toRemove.contains(edge.to)) { edgesWithRemovedRef.add(edge); } else { res.addEdge(edge.from, edge.to); } }); for (T idToRemove : toRemove) { edgesWithRemovedRef.removeIf(edge -> edge.from == idToRemove && edge.to == idToRemove); Set<Edge<T>> incoming = new LinkedHashSet<>(); Set<Edge<T>> outgoing = new LinkedHashSet<>(); for (Edge<T> edge : edgesWithRemovedRef) { if (edge.from == idToRemove) { outgoing.add(edge); } if (edge.to == idToRemove) { incoming.add(edge); } } edgesWithRemovedRef.removeAll(incoming); edgesWithRemovedRef.removeAll(outgoing); incoming.forEach(incomingEdge -> outgoing.forEach(outgoingEdge -> { Edge<T> edge = new Edge<>(incomingEdge.from, outgoingEdge.to); if (toRemove.contains(edge.from) || toRemove.contains(edge.to)) { edgesWithRemovedRef.add(edge); } else { res.addEdge(edge.from, edge.to); } })); } if (!edgesWithRemovedRef.isEmpty()) { throw new <START> IllegalStateException("There <END> should be no remaining edge at this point."); } return res; }</code><technical_language>I add diagnostic info exception: 1) edges remained 2) If number is small (let < 10), try print edges</technical_language>

Target:
public static <T> Digraph<T> buildFilteredDigraph(Digraph<T> initialGraph, Predicate<T> filterOut, Class<T> clazz) { Digraph<T> res = new Digraph<>(clazz); Set<T> toRemoveIds = new LinkedHashSet<>(); for (Vertex<T> vertex : initialGraph.vertexList) { T id = vertex.id; if (!filterOut.test(id)) { res.addVertex(id); } else { toRemoveIds.add(id); } } if (res.vertexList.size() < 2) { return res; } final Set<Edge<T>> edgesToReplace = new LinkedHashSet<>(); initialGraph.getEdges().forEach(edge -> { if (toRemoveIds.contains(edge.from) || toRemoveIds.contains(edge.to)) { edgesToReplace.add(edge); } else { res.addEdge(edge.from, edge.to); } }); for (T toRemove : toRemoveIds) { edgesToReplace.removeIf(edge -> edge.from == toRemove && edge.to == toRemove); Set<Edge<T>> incoming = edgesToReplace.stream().filter(edge -> edge.to == toRemove).collect(Collectors.toSet()); Set<Edge<T>> outgoing = edgesToReplace.stream().filter(edge -> edge.from == toRemove).collect(Collectors.toSet()); edgesToReplace.removeAll(incoming); edgesToReplace.removeAll(outgoing); incoming.forEach(incomingEdge -> outgoing.forEach(outgoingEdge -> { Edge<T> edge = new Edge<>(incomingEdge.from, outgoingEdge.to); if (toRemoveIds.contains(edge.from) || toRemoveIds.contains(edge.to)) { edgesToReplace.add(edge); } else { res.addEdge(edge.from, edge.to); } })); } if (!edgesToReplace.isEmpty()) { throw new IllegalStateException(edgesToReplace.size() + " edges are remaining at this point: " + edgesToReplace.toString() + "\nThere should be none. This indicates a bug in the graph filtering algorithm"); } return res; }