diff --git a/README.md b/README.md index a7fb5a6..8c1b6ec 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,21 @@ # tree-sort +Repository for the tree sort algorithm workshop. + +You'll find the workshop material in the workshop folder. + +## workshop/treesort.py + +Implementation of the binary search tree. When the script is + +``` +python workshop/treesort.py +``` + +## workshop/text-tree.py + +Reads a text (`workshop/text-to-sort.txt`)and generates a visual tree: + +``` +python/text-tree.py +``` \ No newline at end of file diff --git a/workshop/graph_utils.py b/workshop/graph_utils.py new file mode 100644 index 0000000..9c74286 --- /dev/null +++ b/workshop/graph_utils.py @@ -0,0 +1,20 @@ +from string import ascii_uppercase + +# Returns a function to generate node names with given prefix +def make_name_generator (prefix = '', length = 1): + wheels = [{ 'position': None, 'max': len(ascii_uppercase), 'values': list(ascii_uppercase)} for _ in range(length)] + + def name_generator (): + for wheel in wheels: + if wheel['position'] is None: + wheel['position'] = 0 + else: + wheel['position'] += 1 + if wheel['position'] < wheel['max']: + break + else: + wheel['position'] = 0 + + return prefix + ''.join(reversed([wheel['values'][wheel['position']] for wheel in wheels])) + + return name_generator diff --git a/workshop/text-to-sort.txt b/workshop/text-to-sort.txt new file mode 100644 index 0000000..7f07eeb --- /dev/null +++ b/workshop/text-to-sort.txt @@ -0,0 +1 @@ +"the quality of light by which we scrutinize our lives has direct bearing upon the product which we live, and upon the changes which we hope to bring about through those lives. It is within this light that we form those ideas by which we pursue our magic and make it realized. This is poetry as illumination, for it is through poetry that we give name to those ideas which are - until the poem - nameless and formless, about to be birthed, but already felt. That distillation of experience from which true poetry springs births thought as dream births concept, as feeling births idea, as knowledge births (precedes) understanding." \ No newline at end of file diff --git a/workshop/text-tree.py b/workshop/text-tree.py new file mode 100644 index 0000000..7485ee8 --- /dev/null +++ b/workshop/text-tree.py @@ -0,0 +1,48 @@ +from treesort import TreeNode +from visualizer import visualize +from random import shuffle +import os.path + +basepath = os.path.dirname(os.path.abspath(__file__)) + +# Alternative sorting function that has a comparator +def insert (root, value, key = lambda value: value): + if not root: + root = TreeNode() + root.value = value + else: + if value == root.value: + root.value = value + else: + if key(value) < key(root.value): + root.left = insert(root.left, value, key) + else: + root.right = insert(root.right, value, key) + return root + +# Alternative make tree that allows for a callback +# which returns the sorting value. +# By default the sort value is the same as the value +def make_tree (values, key = lambda word: word): + tree = None + + for value in values: + tree = insert(tree, value, key) + + return tree + +def clean (word): + return word.lower().strip(' .,!?-"()[]‘’“”') + +def not_empty (word): + return (word) + +if __name__ == '__main__': + with open(os.path.join(basepath, 'text-to-sort.txt'), 'r') as h: + text = h.read() + words = text.split(' ') + cleaned_words = list(filter(not_empty, map(clean, words))) + + shuffle(cleaned_words) + + visualize(make_tree(cleaned_words), 'text-tree-random') \ No newline at end of file diff --git a/workshop/treesort.py b/workshop/treesort.py new file mode 100644 index 0000000..c20aac8 --- /dev/null +++ b/workshop/treesort.py @@ -0,0 +1,85 @@ +from random import randint + +# Description of a node +class TreeNode (object): + def __init__ (self, value = None): + self.value = value + self.left = None + self.right = None + +""" + This function defines a root for the tree and initiates + the process of looping through the values and inserting + them into the tree. +""" +def make_tree (values): + tree = None + + for value in values: + tree = insert(tree, value) + + return tree + +""" + Adds a node to the tree +""" +def insert (root, value, key = lambda value: value): + # Test whether there is a node at the current location + if not root: + # If not, insert the node here + + root = TreeNode() + root.value = value + else: + # There is a node, compare value to be inserted + # and the value of the node to decide whether the + # node should be attached to the left or the right + if key(value) < key(root.value): + root.left = insert(root.left, value, key) + else: + root.right = insert(root.right, value, key) + return root + +""" + Measures the depth of a (sub)tree +""" +def measure_tree (node): + if node: + # If there is a node, measure the depth + # of the left and right node + depth_left = measure_tree(node.left) + depth_right = measure_tree(node.right) + + # Return the answer with the highest value + # and add one + return max(depth_left, depth_right) + 1 + else: + # There isn't a node, answer with the value 0 + return 0 + + +def traverse_tree (node): + # Test whether there is a node + if node: + # If there is a node, ask its left node to traverse + tree_left = traverse_tree(node.left) + # and ask the same for its right node + tree_right = traverse_tree(node.right) + + # Then combine into a new list: + # the list answered by the left node + # with the value of the node itself + # And the list answered by the right node + # + # Give this new list as an answer to the root node + return tree_left + [ node.value ] + tree_right + else: + # There is no node, return an empty list + return [] + + +if __name__ == '__main__': + values = [randint(0, 100) for _ in range(15)] + tree = make_tree(values) + + print(values, tree.value, tree.left.value if tree.left else None, tree.right.value if tree.right else None, measure_tree(tree), traverse_tree(tree)) \ No newline at end of file diff --git a/workshop/visualizer.py b/workshop/visualizer.py new file mode 100644 index 0000000..5fe9535 --- /dev/null +++ b/workshop/visualizer.py @@ -0,0 +1,38 @@ +from graph_utils import make_name_generator +from graphviz import Digraph, Graph + +generate_node_name = make_name_generator(length=3) + +def visualize_node (graph, node, previous_node_name = None, tailport = None): + if node: + node_name = generate_node_name() + graph.node(node_name, label=str(node.value)) + + if previous_node_name: + graph.edge(previous_node_name, node_name, tailport=tailport, headport='s') + + + visualize_node(graph, node.left, node_name, 'nw') + # Hack to have a more beautiful layout + visualize_node(graph, None, node_name, 'n') + visualize_node(graph, node.right, node_name, 'ne') + + else: + node_name = generate_node_name() + graph.node(node_name, label=node_name, style='invis') + graph.edge(previous_node_name, node_name, style='invis', tailport=tailport, headport='s') + + +def visualize (tree, graphname): + graph = Graph(name=graphname, format='svg', engine='dot') + graph.attr('graph', splines='line', rankdir='BT') + visualize_node (graph, tree) + graph.render(graphname) + +if __name__ == '__main__': + from treesort import make_tree + from random import randint + + values = [randint(0, 250) for _ in range(15)] + tree = make_tree(values) + visualize(tree, 'tree') \ No newline at end of file