py2dot 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. #!/usr/bin/env python
  2. class Graph:
  3. def __init__(self, N):
  4. self.N = N # number of vertices
  5. # maps a vertex number to the list of (vertices, edge value)
  6. # to which it is connected by edges.
  7. self.adjacent = [[] for n in xrange(N)]
  8. def connect(self, vertex1, vertex2, edge_value):
  9. """
  10. Connect 'vertex1' and 'vertex2' with an edge, with associated
  11. value 'value'
  12. """
  13. # Add vertices to each other's adjacent list
  14. self.adjacent[vertex1].append( (vertex2, edge_value) )
  15. self.adjacent[vertex2].append( (vertex1, edge_value) )
  16. def check(self):
  17. """
  18. See if vertex values add up to edge values (mod N).
  19. """
  20. for vertex in xrange(self.N):
  21. for neighbor, edge_value in self.adjacent[vertex]:
  22. assert (self.vertex_values[vertex] +
  23. self.vertex_values[neighbor]) % self.N == edge_value
  24. def calc_tree_sizes(self):
  25. """
  26. After running this method, the attribute size will contain a list,
  27. which maps the vertices to the size of the tree that vertex belongs
  28. to.
  29. """
  30. visited = self.N * [-1] # -1 unvisited, otherwise the number of tree
  31. treenum = 0
  32. # Loop over all vertices, taking unvisited ones as roots.
  33. for root in xrange(self.N):
  34. if visited[root] >= 0:
  35. continue
  36. # explore tree starting at 'root'
  37. # Stack of vertices to visit, a list of tuples (parent, vertex)
  38. tovisit = [ (None, root) ]
  39. while tovisit:
  40. parent, vertex = tovisit.pop()
  41. visited[vertex] = treenum
  42. # Loop over adjacent vertices, but skip the vertex we arrived
  43. # here from the first time it is encountered.
  44. skip = True
  45. for neighbor, edge_value in self.adjacent[vertex]:
  46. if skip and neighbor == parent:
  47. skip = False
  48. continue
  49. if visited[neighbor] >= 0:
  50. # We visited here before, so the graph is cyclic.
  51. exit('Hmm, graph is cyclic.')
  52. tovisit.append( (vertex, neighbor) )
  53. treenum += 1
  54. # maps the tree number to number of vertices within that tree
  55. treesizes = treenum * [0]
  56. for tree in visited:
  57. treesizes[tree] += 1
  58. self.size = [treesizes[visited[v]] for v in xrange(self.N)]
  59. if verbose:
  60. freq = (self.N+1) * [0]
  61. for size in treesizes:
  62. freq[size] += 1
  63. sys.stderr.write(' Size Trees\n')
  64. for i, f in enumerate(freq):
  65. if f:
  66. sys.stderr.write('%5i %5i\n' % (i, f))
  67. if i == minsize-1:
  68. sys.stderr.write('--------------\n')
  69. def write(self, fo, labels = False):
  70. self.calc_tree_sizes()
  71. fo.write('graph G {\n'
  72. ' size = "8,8";\n'
  73. ' edge [color="#ff0000"]\n')
  74. if labels:
  75. fo.write(' node [color="#a0e0ee", style=filled];\n')
  76. for vertex, value in enumerate(self.vertex_values):
  77. if self.size[vertex] < minsize: continue
  78. fo.write(' { node [label="%i: %i"] v%i }\n' % (
  79. vertex, value, vertex))
  80. else:
  81. fo.write(' node [color="#3377a0", label="",\n'
  82. ' style=filled, shape=circle]\n')
  83. for vertex in xrange(self.N): # edges
  84. if self.size[vertex] < minsize: continue
  85. for neighbor, edge_value in self.adjacent[vertex]:
  86. if neighbor > vertex: continue
  87. fo.write(' v%i -- v%i%s;\n' %
  88. (vertex, neighbor,
  89. (' [label="%s: %i"]' % (K[edge_value], edge_value))
  90. if labels else ''))
  91. fo.write('}\n')
  92. fo.close()
  93. if __name__ == '__main__':
  94. import sys
  95. from optparse import OptionParser
  96. usage = "usage: %prog [options] [PYCODE]"
  97. description = """\
  98. Given the python code for a perfect hash function which was generated by
  99. perfect_hash.py, e.g. by '$ ../perfect_hash.py animals.txt >animals.py',
  100. this program will create the graph which was used in determining the
  101. perfect hash function. The input python code may also be given to stdin.
  102. The output is saved as in the .dot format which is used by the Graphviz
  103. tools (see http://www.graphviz.org/) to generate a picture of the graph.
  104. """
  105. parser = OptionParser(usage = usage,
  106. description = description,
  107. prog = sys.argv[0])
  108. parser.add_option("-l", "--labels",
  109. action = "store_true",
  110. help = "Be verbose")
  111. parser.add_option("-m", "--minsize",
  112. action = "store",
  113. default = 1,
  114. type = "int",
  115. help = "Include only trees in the output which "
  116. "have at least INT vertices. "
  117. "Default is %default, i.e. all trees are "
  118. "included within the output.",
  119. metavar = "INT")
  120. parser.add_option("-o", "--output",
  121. action = "store",
  122. help = "Specify output FILE explicitly. "
  123. "Default, is stdout. ",
  124. metavar = "FILE")
  125. parser.add_option("-v", "--verbose",
  126. action = "store_true",
  127. help = "Be verbose")
  128. options, args = parser.parse_args()
  129. if options.minsize > 0:
  130. minsize = options.minsize
  131. else:
  132. parser.error("minimal size of trees has to be larger than zero")
  133. verbose = options.verbose
  134. if len(args) > 1:
  135. parser.error("incorrect number of arguments")
  136. # --------------------- end parsing and checking -----------------------
  137. if verbose:
  138. sys.stderr.write('minsize (of trees): %i\n' % minsize)
  139. sys.stderr.write('labels (in output): %s\n' % options.labels)
  140. # ------------ input filehandle
  141. if len(args)==1:
  142. try:
  143. fi = file(args[0])
  144. except IOError :
  145. exit("Error: Can't open `%s' for reading." % args[0])
  146. else:
  147. fi = sys.stdin
  148. # ------------ read input, i.e. execute code
  149. exec(fi.read())
  150. # ------------ make graph
  151. g = Graph(len(G))
  152. g.vertex_values = G
  153. for key, hashval in zip(K, H):
  154. g.connect(hash_f(key, S1),
  155. hash_f(key, S2),
  156. hashval)
  157. g.check()
  158. # ------------ output filehandle
  159. if options.output:
  160. try:
  161. fo = file(options.output, 'w')
  162. except IOError :
  163. exit("Error: Can't open `%s' for writing." % options.output)
  164. else:
  165. fo = sys.stdout
  166. # ------------ write output, i.e. generate .dot output
  167. g.write(fo, options.labels)
  168. # Local Variables:
  169. # mode: python
  170. # End: