{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Treating Trees II: `treestep`\n", "Let's consider a tree structure, similar to one described [\"Why functional programming matters\" by John Hughes](https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf), that consists of a node value followed by zero or more child trees. (The asterisk is meant to indicate the [Kleene star](https://en.wikipedia.org/wiki/Kleene_star).)\n", "\n", " tree = [] | [node tree*]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the spirit of `step` we are going to define a combinator `treestep` which expects a tree and three additional items: a base-case function `[B]`, and two quoted programs `[N]` and `[C]`.\n", "\n", " tree [B] [N] [C] treestep" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the current tree node is empty then just execute `B`:\n", "\n", " [] [B] [N] [C] treestep\n", " ---------------------------\n", " [] B" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Otherwise, evaluate `N` on the node value, `map` the whole function (abbreviated here as `K`) over the child trees recursively, and then combine the result with `C`.\n", "\n", " [node tree*] [B] [N] [C] treestep\n", " --------------------------------------- w/ K == [B] [N] [C] treestep\n", " node N [tree*] [K] map C\n", "\n", "(Later on we'll experiment with making `map` part of `C` so you can use other combinators.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Derive the recursive function.\n", "We can begin to derive it by finding the `ifte` stage that `genrec` will produce.\n", "\n", " K == [not] [B] [R0] [R1] genrec\n", " == [not] [B] [R0 [K] R1] ifte\n", "\n", "So we just have to derive `J`:\n", "\n", " J == R0 [K] R1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The behavior of `J` is to accept a (non-empty) tree node and arrive at the desired outcome.\n", "\n", " [node tree*] J\n", " ------------------------------\n", " node N [tree*] [K] map C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So `J` will have some form like:\n", "\n", " J == ... [N] ... [K] ... [C] ..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's dive in. First, unquote the node and `dip` `N`.\n", "\n", " [node tree*] uncons [N] dip\n", " node [tree*] [N] dip\n", " node N [tree*]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, `map` `K` over the child trees and combine with `C`.\n", "\n", " node N [tree*] [K] map C\n", " node N [tree*] [K] map C\n", " node N [K.tree*] C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So:\n", "\n", " J == uncons [N] dip [K] map C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plug it in and convert to `genrec`:\n", "\n", " K == [not] [B] [J ] ifte\n", " == [not] [B] [uncons [N] dip [K] map C] ifte\n", " == [not] [B] [uncons [N] dip] [map C] genrec" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Extract the givens to parameterize the program.\n", "Working backwards:\n", "\n", " [not] [B] [uncons [N] dip] [map C] genrec\n", " [B] [not] swap [uncons [N] dip] [map C] genrec\n", " [B] [uncons [N] dip] [[not] swap] dip [map C] genrec\n", " ^^^^^^^^^^^^^^^^\n", " [B] [[N] dip] [uncons] swoncat [[not] swap] dip [map C] genrec\n", " [B] [N] [dip] cons [uncons] swoncat [[not] swap] dip [map C] genrec\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Extract a couple of auxiliary definitions:\n", "\n", " TS.0 == [[not] swap] dip\n", " TS.1 == [dip] cons [uncons] swoncat" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " [B] [N] TS.1 TS.0 [map C] genrec\n", " [B] [N] [map C] [TS.1 TS.0] dip genrec\n", " [B] [N] [C] [map] swoncat [TS.1 TS.0] dip genrec\n", "\n", "The givens are all to the left so we have our definition." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### (alternate) Extract the givens to parameterize the program.\n", "Working backwards:\n", "\n", " [not] [B] [uncons [N] dip] [map C] genrec\n", " [not] [B] [N] [dip] cons [uncons] swoncat [map C] genrec\n", " [B] [N] [not] roll> [dip] cons [uncons] swoncat [map C] genrec\n", " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Define `treestep`" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from notebook_preamble import D, J, V, define, DefinitionWrapper" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "DefinitionWrapper.add_definitions('''\n", "\n", " _treestep_0 == [[not] swap] dip\n", " _treestep_1 == [dip] cons [uncons] swoncat\n", " treegrind == [_treestep_1 _treestep_0] dip genrec\n", " treestep == [map] swoncat treegrind\n", "\n", "''', D)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Examples\n", "Consider trees, the nodes of which are integers. We can find the sum of all nodes in a tree with this function:\n", "\n", " sumtree == [pop 0] [] [sum +] treestep" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "define('sumtree == [pop 0] [] [sum +] treestep')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Running this function on an empty tree value gives zero:\n", "\n", " [] [pop 0] [] [sum +] treestep\n", " ------------------------------------\n", " 0" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n" ] } ], "source": [ "J('[] sumtree') # Empty tree." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Running it on a non-empty node:\n", "\n", " [n tree*] [pop 0] [] [sum +] treestep\n", " n [tree*] [[pop 0] [] [sum +] treestep] map sum +\n", " n [ ... ] sum +\n", " n m +\n", " n+m\n" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "23\n" ] } ], "source": [ "J('[23] sumtree') # No child trees." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "23\n" ] } ], "source": [ "J('[23 []] sumtree') # Child tree, empty." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "32\n" ] } ], "source": [ "J('[23 [2 [4]] [3]] sumtree') # Non-empty child trees." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "49\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] sumtree') # Etc..." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "49\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [] [cons sum] treestep') # Alternate \"spelling\"." ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[23 [23 [23] [23]] [23] [23 []]]\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 23] [cons] treestep') # Replace each node." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 [1 [1] [1]] [1] [1 []]]\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep')" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] [] [pop 1] [cons] treestep sumtree')" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n" ] } ], "source": [ "J('[23 [2 [8] [9]] [3] [4 []]] [pop 0] [pop 1] [sum +] treestep') # Combine replace and sum into one function." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "scrolled": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3\n" ] } ], "source": [ "J('[4 [3 [] [7]]] [pop 0] [pop 1] [sum +] treestep') # Combine replace and sum into one function." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Redefining the Ordered Binary Tree in terms of `treestep`.\n", "\n", " Tree = [] | [[key value] left right]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What kind of functions can we write for this with our `treestep`?\n", "\n", "The pattern for processing a non-empty node is:\n", "\n", " node N [tree*] [K] map C\n", "\n", "Plugging in our BTree structure:\n", "\n", " [key value] N [left right] [K] map C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Traversal\n", " [key value] first [left right] [K] map i\n", " key [value] [left right] [K] map i\n", " key [left right] [K] map i\n", " key [lkey rkey ] i\n", " key lkey rkey" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This doesn't quite work:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "3 'B' 'B'\n" ] } ], "source": [ "J('[[3 0] [[2 0] [][]] [[9 0] [[5 0] [[4 0] [][]] [[8 0] [[6 0] [] [[7 0] [][]]][]]][]]] [\"B\"] [first] [i] treestep')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Doesn't work because `map` extracts the `first` item of whatever its mapped function produces. We have to return a list, rather than depositing our results directly on the stack.\n", "\n", "\n", " [key value] N [left right] [K] map C\n", "\n", " [key value] first [left right] [K] map flatten cons\n", " key [left right] [K] map flatten cons\n", " key [[lk] [rk] ] flatten cons\n", " key [ lk rk ] cons\n", " [key lk rk ]\n", "\n", "So:\n", "\n", " [] [first] [flatten cons] treestep" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[3 2 9 5 4 8 6 7]\n" ] } ], "source": [ "J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [first] [flatten cons] treestep')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There we go." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### In-order traversal\n", "\n", "From here:\n", "\n", " key [[lk] [rk]] C\n", " key [[lk] [rk]] i\n", " key [lk] [rk] roll<\n", " [lk] [rk] key swons concat\n", " [lk] [key rk] concat\n", " [lk key rk]\n", "\n", "So:\n", "\n", " [] [i roll< swons concat] [first] treestep" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[2 3 4 5 6 7 8 9]\n" ] } ], "source": [ "J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [uncons pop] [i roll< swons concat] treestep')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## With `treegrind`?\n", "The `treegrind` function doesn't include the `map` combinator, so the `[C]` function must arrange to use some combinator on the quoted recursive copy `[K]`. With this function, the pattern for processing a non-empty node is:\n", "\n", " node N [tree*] [K] C\n", "\n", "Plugging in our BTree structure:\n", "\n", " [key value] N [left right] [K] C" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['key' 'value'] 'N' [['left'] ['right']] [[not] ['B'] [uncons ['N'] dip] ['C'] genrec] 'C'\n" ] } ], "source": [ "J('[[\"key\" \"value\"] [\"left\"] [\"right\"] ] [\"B\"] [\"N\"] [\"C\"] treegrind')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## `treegrind` with `step`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Iteration through the nodes" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[3 0] 'N' [2 0] 'N' [9 0] 'N' [5 0] 'N' [4 0] 'N' [8 0] 'N' [6 0] 'N' [7 0] 'N'\n" ] } ], "source": [ "J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [pop] [\"N\"] [step] treegrind')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sum the nodes' keys." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "44\n" ] } ], "source": [ "J('0 [[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [pop] [first +] [step] treegrind')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Rebuild the tree using `map` (imitating `treestep`.)" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[103 0] [[102 0] [] []] [[109 0] [[105 0] [[104 0] [] []] [[108 0] [[106 0] [] [[107 0] [] []]] []]] []]]\n" ] } ], "source": [ "J('[[3 0] [[2 0] [] []] [[9 0] [[5 0] [[4 0] [] []] [[8 0] [[6 0] [] [[7 0] [] []]] []]] []]] [] [[100 +] infra] [map cons] treegrind')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Do we have the flexibility to reimplement `Tree-get`?\n", "I think we do:\n", "\n", " [B] [N] [C] treegrind" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We'll start by saying that the base-case (the key is not in the tree) is user defined, and the per-node function is just the query key literal:\n", "\n", " [B] [query_key] [C] treegrind" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This means we just have to define `C` from:\n", "\n", " [key value] query_key [left right] [K] C\n", " " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try `cmp`:\n", "\n", " C == P [T>] [E] [T<] cmp\n", "\n", " [key value] query_key [left right] [K] P [T>] [E] [T<] cmp" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The predicate `P`\n", "Seems pretty easy (we must preserve the value in case the keys are equal):\n", "\n", " [key value] query_key [left right] [K] P\n", " [key value] query_key [left right] [K] roll<\n", " [key value] [left right] [K] query_key [roll< uncons swap] dip\n", "\n", " [key value] [left right] [K] roll< uncons swap query_key\n", " [left right] [K] [key value] uncons swap query_key\n", " [left right] [K] key [value] swap query_key\n", " [left right] [K] [value] key query_key\n", "\n", " P == roll< [roll< uncons swap] dip\n", "\n", "(Possibly with a swap at the end? Or just swap `T<` and `T>`.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So now:\n", "\n", " [left right] [K] [value] key query_key [T>] [E] [T<] cmp\n", "\n", "Becomes one of these three:\n", "\n", " [left right] [K] [value] T>\n", " [left right] [K] [value] E\n", " [left right] [K] [value] T<\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `E`\n", "Easy.\n", "\n", " E == roll> popop first" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `T<` and `T>`\n", "\n", " T< == pop [first] dip i\n", " T> == pop [second] dip i" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Putting it together\n", "\n", "\n", " T> == pop [first] dip i\n", " T< == pop [second] dip i\n", " E == roll> popop first\n", " P == roll< [roll< uncons swap] dip\n", " \n", " Tree-get == [P [T>] [E] [T<] cmp] treegrind\n", "\n", "To me, that seems simpler than the `genrec` version." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "DefinitionWrapper.add_definitions('''\n", "\n", " T> == pop [first] dip i\n", " T< == pop [second] dip i\n", " E == roll> popop first\n", " P == roll< [roll< uncons swap] dip\n", "\n", " Tree-get == [P [T>] [E] [T<] cmp] treegrind\n", "\n", "''', D)" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "15\n" ] } ], "source": [ "J('''\\\n", "\n", "[[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]\n", "\n", "[] [5] Tree-get\n", "\n", "''')" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "'nope'\n" ] } ], "source": [ "J('''\\\n", "\n", "[[3 13] [[2 12] [] []] [[9 19] [[5 15] [[4 14] [] []] [[8 18] [[6 16] [] [[7 17] [] []]] []]] []]]\n", "\n", "[pop \"nope\"] [25] Tree-get\n", "\n", "''')" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 2 }