127 lines
5.3 KiB
Python
127 lines
5.3 KiB
Python
|
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")
|