code: public Graph<T> reduce(Graph<T> g) {
          boolean subgraph = nodes.containsAll(g.nodes) &&
                  g.edges.keySet().stream()
                         .allMatch(u -> adjacentNodes(u).containsAll(g.adjacentNodes(u)));
          if (!subgraph) {
              throw new IllegalArgumentException(g + " is not a subgraph of " + this);
          }
  
          Builder<T> builder = new Builder<>();
          nodes.stream()
                  .forEach(u -> {
                      builder.addNode(u);
                      // filter the edge if there exists a path from u to v in the given g
                      // or there exists another path from u to v in this graph
                      edges.get(u).stream()
                           .filter(v -> !g.pathExists(u, v) && !pathExists(u, v, false))
                           .forEach(v -> builder.addEdge(u, v));
                  });
  
          // add the overlapped edges from this graph and the given g
          g.edges().keySet().stream()
                  .forEach(u -> g.adjacentNodes(u).stream()
                                  .filter(v -> isAdjacent(u, v))
                                  .forEach(v -> builder.addEdge(u, v)));
          return builder.build().reduce();
      }
docs: Returns a new Graph after transitive reduction.  All edges in
  the given g takes precedence over this graph.
  
  @throw IllegalArgumentException g must be a subgraph this graph