shooflenet/articles/tamari/tamari.py

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")