mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 12:03:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			140 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			140 lines
		
	
	
		
			3.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
 | |
| from collections import defaultdict
 | |
| 
 | |
| from typing import Callable, DefaultDict, Iterator, List, Optional, Set, Tuple
 | |
| 
 | |
| Edge = Tuple[str, str]
 | |
| EdgeSet = Set[Edge]
 | |
| 
 | |
| class Graph:
 | |
|     def __init__(self, tuples):
 | |
|         # type: (EdgeSet) -> None
 | |
|         self.children = defaultdict(list)  # type: DefaultDict[str, List[str]]
 | |
|         self.parents = defaultdict(list)  # type: DefaultDict[str, List[str]]
 | |
|         self.nodes = set()  # type: Set[str]
 | |
| 
 | |
|         for parent, child in tuples:
 | |
|             self.parents[child].append(parent)
 | |
|             self.children[parent].append(child)
 | |
|             self.nodes.add(parent)
 | |
|             self.nodes.add(child)
 | |
| 
 | |
|     def copy(self):
 | |
|         # type: () -> 'Graph'
 | |
|         return Graph(self.edges())
 | |
| 
 | |
|     def num_edges(self):
 | |
|         # type: () -> int
 | |
|         return len(self.edges())
 | |
| 
 | |
|     def minus_edge(self, edge):
 | |
|         # type: (Edge) -> 'Graph'
 | |
|         edges = self.edges().copy()
 | |
|         edges.remove(edge)
 | |
|         return Graph(edges)
 | |
| 
 | |
|     def edges(self):
 | |
|         # type: () -> EdgeSet
 | |
|         s = set()
 | |
|         for parent in self.nodes:
 | |
|             for child in self.children[parent]:
 | |
|                 s.add((parent, child))
 | |
|         return s
 | |
| 
 | |
|     def remove_exterior_nodes(self):
 | |
|         # type: () -> None
 | |
|         still_work_to_do = True
 | |
|         while still_work_to_do:
 | |
|             still_work_to_do = False  # for now
 | |
|             for node in self.nodes:
 | |
|                 if self.is_exterior_node(node):
 | |
|                     self.remove(node)
 | |
|                     still_work_to_do = True
 | |
|                     break
 | |
| 
 | |
|     def is_exterior_node(self, node):
 | |
|         # type: (str) -> bool
 | |
|         parents = self.parents[node]
 | |
|         children = self.children[node]
 | |
|         if not parents:
 | |
|             return True
 | |
|         if not children:
 | |
|             return True
 | |
|         if len(parents) > 1 or len(children) > 1:
 | |
|             return False
 | |
| 
 | |
|         # If our only parent and child are the same node, then we could
 | |
|         # effectively be collapsed into the parent, so don't add clutter.
 | |
|         return parents[0] == children[0]
 | |
| 
 | |
|     def remove(self, node):
 | |
|         # type: (str) -> None
 | |
|         for parent in self.parents[node]:
 | |
|             self.children[parent].remove(node)
 | |
|         for child in self.children[node]:
 | |
|             self.parents[child].remove(node)
 | |
|         self.nodes.remove(node)
 | |
| 
 | |
|     def report(self):
 | |
|         # type: () -> None
 | |
|         print('parents/children/module')
 | |
|         tups = sorted([
 | |
|             (len(self.parents[node]), len(self.children[node]), node)
 | |
|             for node in self.nodes])
 | |
|         for tup in tups:
 | |
|             print(tup)
 | |
| 
 | |
| def best_edge_to_remove(orig_graph, is_exempt):
 | |
|     # type: (Graph, Callable[[Edge], bool]) -> Optional[Edge]
 | |
|     # expects an already reduced graph as input
 | |
| 
 | |
|     orig_edges = orig_graph.edges()
 | |
| 
 | |
|     def get_choices():
 | |
|         # type: () -> Iterator[Tuple[int, Edge]]
 | |
|         for edge in orig_edges:
 | |
|             if is_exempt(edge):
 | |
|                 continue
 | |
|             graph = orig_graph.minus_edge(edge)
 | |
|             graph.remove_exterior_nodes()
 | |
|             size = graph.num_edges()
 | |
|             yield (size, edge)
 | |
| 
 | |
|     choices = list(get_choices())
 | |
|     if not choices:
 | |
|         return None
 | |
|     min_size, best_edge = min(choices)
 | |
|     if min_size >= orig_graph.num_edges():
 | |
|         raise Exception('no edges work here')
 | |
|     return best_edge
 | |
| 
 | |
| def make_dot_file(graph):
 | |
|     # type: (Graph) -> str
 | |
|     buffer = 'digraph G {\n'
 | |
|     for node in graph.nodes:
 | |
|         buffer += node + ';\n'
 | |
|         for child in graph.children[node]:
 | |
|             buffer += '{} -> {};\n'.format(node, child)
 | |
|     buffer += '}'
 | |
|     return buffer
 | |
| 
 | |
| def test():
 | |
|     # type: () -> None
 | |
|     graph = Graph(set([
 | |
|         ('x', 'a'),
 | |
|         ('a', 'b'),
 | |
|         ('b', 'c'),
 | |
|         ('c', 'a'),
 | |
|         ('c', 'd'),
 | |
|         ('d', 'e'),
 | |
|         ('e', 'f'),
 | |
|         ('e', 'g'),
 | |
|     ]))
 | |
|     graph.remove_exterior_nodes()
 | |
| 
 | |
|     s = make_dot_file(graph)
 | |
|     open('zulip-deps.dot', 'w').write(s)
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     test()
 |