code: def transitive_reduction(self):
          
          combinations = []
          for node, edges in self.graph.items():
              combinations += [[node, edge] for edge in edges]
  
          while True:
              new_combinations = []
              for comb1 in combinations:
                  for comb2 in combinations:
                      if not comb1[-1] == comb2[0]:
                          continue
                      new_entry = comb1 + comb2[1:]
                      if new_entry not in combinations:
                          new_combinations.append(new_entry)
              if not new_combinations:
                  break
              combinations += new_combinations
  
          constructed = {(c[0], c[-1]) for c in combinations if len(c) != 2}
          for node, edges in self.graph.items():
              bad_nodes = {e for n, e in constructed if node == n}
              self.graph[node] = edges - bad_nodes
docs: Performs a transitive reduction on the DAG. The transitive
          reduction of a graph is a graph with as few edges as possible with the
          same reachability as the original graph.
  
          See https://en.wikipedia.org/wiki/Transitive_reduction