import sys import argparse parser = argparse.ArgumentParser() parser.add_argument("-l", "--length", help="length of the initial string", type=int, default=5) parser.add_argument("output", help="file to output to", type=argparse.FileType('w'), nargs="?", default=sys.stdout) args = parser.parse_args() node_marker = 'O' class TreeNode: """A binary tree node for the purpose of doing tree manipulations and generating tamari lattices. Not very pretty code.""" def __init__(self,l,v,r): """Arguments are: left node, payload, right node.""" self.l = l self.v = v self.r = r def __str__(self): """Return the string which would be parsed into this object.""" if self.l is None or self.r is None: return str(self.v) return node_marker + str(self.l) + str(self.r) def pretty_string(self): """Return a string suitable for a label.""" if self.l is None or self.r is None: return str(self.v) return "(" + self.l.pretty_string() + "," + self.r.pretty_string() + ")" def rotate_left(self): if not leaf(self) and not leaf(self.r): self.l = TreeNode(self.l, None, self.r.l) self.r = self.r.r def rotate_right(self): if not leaf(self) and not leaf(self.l): self.r = TreeNode(self.l.r, None, self.r) self.l = self.l.l def clone(self): if leaf(self): return TreeNode(None, self.v, None) else: return TreeNode(self.l.clone(), self.v, self.r.clone()) def count_nodes(self): if leaf(self): return 0 else: return self.l.count_nodes() + self.r.count_nodes() + 1 def do_on_nth_node(self, n, f): """Call the second argument on the nth node in the tree, going depth first from left to right.""" if leaf(self): return n remaining = self.l.do_on_nth_node(n,f) if remaining is 0: return 0 if remaining is 1: f(self) return 0 return self.r.do_on_nth_node(remaining-1,f) def __eq__(self, other): """Comparison for sets. Just checks if the string representation is the same.""" return str(self) == str(other) def __hash__(self): """Much like __eq__, this is needed for using this in sets. This just hashes the string representation.""" return hash(str(self)) def nth_node_helper(root, n, f): """This is so ugly. I just want to do something on the nth node and return the transformed tree! ... But it works. Note: don't extend this script ever. It should stay in its little awful box.""" root.do_on_nth_node(n, f) return root def leaf(node): return node.l is None or node.r is None def generate_children(root): """Return a set of all the trees produced by rotating left at one point in the tree that is passed in.""" # If there are n nodes in the tree, then there are at most n places to do a rotation. # I didn't feel like figuring out a more direct method to generate all rotations, so I just did this: # Try rotating at each child node. Remove all the ones that weren't changed (that is, where it failed). children = set(nth_node_helper(root.clone(), i, TreeNode.rotate_left) for i in range(root.count_nodes())) return children - {root} # Return the children, except without the root. We don't care about the root. def _parse(s): """Parse one token of the string passed in, and return a tuple: (the tree we parsed, the remainer that wasn't parsed). This function recursively calls itself, to parse the whole string. """ if s[0] is node_marker: first, remainder = _parse(s[1:]) second, secondRemainder = _parse(remainder) return (TreeNode(first, None, second), secondRemainder) return (TreeNode(None, s[0], None), s[1:]) def parse(string): """Parses a string, assuming the node marker in node_marker, and returns a TreeNode. This is a wrapper around _parse.""" return _parse(string)[0] # The string we start with is just a slice of the alphabet, with length specified by the command line argument -l. root_string = "abcdefghijklmnopqrstuvwxyz"[:args.length] root_string = "".join( "O" + char for char in root_string ) root_string = "".join( root_string.rsplit("O", 1) ) # helper function for generating node labels label_from_node = lambda x: "{name} [label=\"{label}\"];".format(name=str(x), label=x.pretty_string()) # Here's the root we start with! RootOfAllEvil = parse(root_string) #print(RootOfAllEvil) current_generation = set() next_generation = {RootOfAllEvil} edges = set() labels = set() while len(current_generation ^ next_generation): # So long as we haven't reached stability... # Move to look at the next generation. #print("current_generation: {}, next_generation: {}".format(current_generation, next_generation)) current_generation = next_generation # Clear out the next generation so that we can fill it with the children of the now-current generation. next_generation = set() #print("current_generation: {}, next_generation: {}".format(current_generation, next_generation)) labels = labels | set(label_from_node(parent) for parent in current_generation) for parent in current_generation: children = generate_children(parent) #print("children of {parent}: {children}".format(parent=parent, children=children)) labels = labels | set(label_from_node(child) for child in children) edges = edges | set(str(parent) + " -> " + str(child) + ";" for child in children) next_generation = next_generation | children args.output.write(u"digraph tamari {\n") args.output.write(u"\n".join(labels) + "\n") args.output.write(u"\n".join(edges)) args.output.write(u"\n}\n")