Recursion and Ajax

Ajax is not lengthy, but it is odd and surprising.

We'll look first at recursion, since it's the harder part of the assignment.

Plan

  1. Exercise from last time
  2. Discuss A9, 20 Questions
  3. Briefly about Cloning and Event delegation
  4. Answer your recursion questions
  5. Explore Recursion
  6. Recap Ajax
  7. Answer your Ajax questions

Exercise

Let's discuss the exercise from last time

Assignment 9

We'll look at Assignment 9 and I'll give a demo.

Observations

  1. You'll have to manage and work with trees
  2. You'll have to dynamically create pairs of clickable buttons
  3. You'll have to have a recursive function to show the tree
  4. Playing the game will involve tracing a single path through the tree from root to leaf. That task is not recursive.

Let's look at some of these issues.

Cloning

Here's a complex bit of HTML:

:::HTML
<div id="template">
<div class="foo">
   <div class="bar">
      <p><span style="font-weight:bold">buttons!</span>
         <span data-msg="msg">lorem ipsum</span>
         <button data-kind="fred">fred</button>
         <button data-kind="george">george</button>
      </p>
   </div>
</div>
</div>

Here's what it looks like:

buttons! lorem ipsum

We will add copies of that template to a container (id of container). Here's the JavaScript code that will do the work:

:::JavaScript
$("#addToContainer").click(function () {
     var clone = $("#template .foo").clone();
     var num = Math.floor(100*Math.random());
     clone.find("[data-msg]").text(num);
     clone.appendTo("#container");
});

Let's see it in action. Here's the button and the container:

This is the container

The button makes it easy to add custom copies to the container, no matter how complicated the HTML.

We can hide the template using display:none in our CSS.

Delegation

Suppose we want to add behavior to our buttons. The following is one very good way. Note that these handlers are added when the page loads, before any of the buttons exists!

:::JavaScript
$("#container").on('click',
                   '[data-kind=fred]',
                   function (event) {
                      alert("Oh, Fred!");
                   });
$("#container").on('click',
                   '[data-kind=george]',
                   function (event) {
                      var num = $(event.target)
                                    .closest(".bar")
                                    .find("[data-msg]")
                                    .text();
                      alert("Heavens, George! number is "+num);
                   });

Recursion

Trees are important in Computer Science. They are best defined recursively and therefore are an excellent match for recursive algorithms. It's essential that you are comfortable with such algorithms and data structures.

Recursive Definition:

A binary tree is either:

  • a leaf (a node with no children), OR
  • a node with two children, each of which is a binary tree

Your Recursion Questions

I'll answer your Recursion questions

Recursive Tree Traversal

I've loaded into this page a tree in JSON as the value of treejs. We'll take a look at it. Here's a screenshot of it all opened up:

treejs in the JS console

Here's a differently drawn figure:

treejs as a normal tree

Note that an important feature of recursion is determining the base case(s). In this tree, the base case is when the child node is a string as opposed to another JS object.

Here's how we can define that:

:::JavaScript
function isLeaf(node) {
    return( typeof node === 'string' );
}

Tree Size

The size of a tree (number of nodes, counting the leaves) can be defined recursively. How?

In English:

  • a leaf is of size 1
  • a non-leaf is of size 1 plus the size of each child

Here's code to compute that:

:::JavaScript
function treeSize(node) {
    if( isLeaf(node) ) {
        return 1;
    } else {
        return (1 
                + treeSize(node.Y) 
                + treeSize(node.N));
    }
}

I suggest copying that into your notes right now, so you can refer to it for the rest of class.

Notes: this function is very short and simple to write. It looks almost magical. Furthermore, any non-recursive solution would be much more complicated. So, recursion is very important in computer science.

Verbose Tree Size

Understanding how treeSize() works can be confusing. Here's a very chatty version of it:

#!JS
function treeSizeChatty(node,level) {
    if( isLeaf(node) ) {
        return 1;
    } else {
        var i = indent(level);
        console.log(i+'Computing size of node '+node.Q);
        let y = treeSizeChatty(node.Y,level+1);
        let n = treeSizeChatty(node.N,level+1);
        let total = 1+y+n;
        // template literal
        console.log(i+`Size is 1+${y}+${n} or ${total}`)
        return total;
    }
}

Try it out, like this: treeSizeChatty(treejs,0)

You can also try it on subtrees, like treejs.Y and treejs.N

Variations

Here are some thought questions. What does the function compute in each of these cases?

  1. What happens if we return zero for a leaf instead of 1?
  2. What happens if, on non-leaves, we return zero plus the sum of the sizes of the children?
  3. What happens if, for leaves, we return the leaf instead of 1?
  4. What happens if, for leaves, we return a one-element array of the leaf, and for non-leaves, we return the concatenation of the arrays returned for each child?
  1. Since leaves don't count, the function ends up counting just the number of non-leaves. We could call this function `notleafcount`
  2. Since we don't count 1 for each non-leaf, the function ends up counting just the leaves. We could call this function `leafcount`
  3. If we return the leaf (a string), and we use "+" in the recursive case, we end up concatenating together all the leaves, albeit with some "1" characters thrown in. Not a very useful function, though if we remove the "1" characters, it becomes almost useful.
  4. We end up returning an array containing all the leaves. We've "flattened" the tree, which might be a good name for this function.

Flattening

The example of flattening is interesting enough to spend a few minutes on. (Note that this is not the technique you should use in A9. We're just trying to learn recursion right now. In A9, you will be returning structure from the recursion, but you will not be trying to flatten the tree.)

function flatten(node) {
    if( isLeaf(node) ) {
        return [ node ];
    } else {
        var lchild = flatten(node.Y);
        var rchild = flatten(node.N);
        return lchild.concat(rchild);
    }
}

Try it! We could also name this function leavesOnly.

Do you remember Divide-Conquer-Glue from CS 111? Recursion is all about DCG. In the flatten case, the glue is concat, but we've also used +. The divide is to go from a node to its children, which are, by definition, smaller.

How could we include the questions as well?

Exercise:

Write a function to find the height of the tree. It might be helpful to know that there is a Math.max function in JavaScript.

I suggest you first write it out in English and then try to code it using pencil and paper; don't try to do live coding in class, unless you have VSC up and running.

function treeHeight(node) {
    if( isLeaf(node) ) {
        return 1;
    } else {
        return 1 + Math.max(treeHeight(node.Y),
                            treeHeight(node.N));
    }
}

We'll copy/paste that into the JS console and try it out. Try it on subtrees, too.

Exercise: allAnswers

Write a function to return a string comprising all the answers in the tree. The strategy on the recursive step is to get the string of answers from each child and then concatenate those two strings together.

function allAnswers(node) {
    if( isLeaf(node) ) {
        // the node itself is the answer.
        // newline just for readability
        return node+"\n";
    } else {
        var yc = allAnswers(node.Y);
        var nc = allAnswers(node.N);
        return yc+nc;
    }
}

This is just our flatten function! Did you figure that out?

Again, we'll copy/paste that into the JS console and try it out.

Exercise: allAnswersArray

The last solution was okay, but strings aren't the nicest data structure, though we could split the string.

Instead, let's do a variant in which we return an array of all the answers.

You should know that there is a concat method on JavaScript arrays.

function allAnswersArray(node) {
    if( isLeaf(node) ) {
        // the node itself is the answer.
        // must return an array, though
        return [ node ];
    } else {
        var yc = allAnswersArray(node.Y);
        var nc = allAnswersArray(node.N);
        return yc.concat(nc);
    }
}

Exercise: allQuestions

Write a function to return an array comprising all the questions in the tree.

The strategy on the recursive step is to get all the questions from both children and then concatenate those two strings together along with the question from this node.

function allQuestionsArray(node) {
    if( isLeaf(node) ) {
        return [];  // leaves don't have a question
    } else {
        var self = [ node.Q ];
        var yc = allQuestionsArray(node.Y);
        var nc = allQuestionsArray(node.N);
        return self.concat(yc,nc)
    }
}

Recursion with a "driver" function

Often we want to do something with the result of recursive function. That's sometimes an entirely separate, unrelated function, or sometimes is thought of as a "driver" function. Here's an example of the latter.

Suppose we want to get a list of the answers in a tree and show them on the page. Here's a function that does that:

:::JavaScript
function display_answer_list(where) {
    var answers = allAnswersArray(treejs);
    var dst = $(where).one();
    dst.empty();
    answers.forEach(function (ans) {
        $("<li>").text(ans).appendTo(dst);
    })
}

Try it with #answer-list:

    Note that the function above is an example of a driver function: a function that invokes a recursive function with the correct first argument and deals with the result.

    Recap Ajax

    Ajax makes a "side" request to the server and sets up a "catcher" to catch and deal with the response.

    slideshow

    Collatz Example

    Timing

    Which message is printed first?

    :::JavaScript
    $.get('characters.json',function (data) {
        console.log('Done. Got '+data);
    });
    console.log('Finally done.');
    

    Demo

    get random HP character

    Answer the following questions:

    1. How long does it take for the data to arrive?
    2. Does it take the same amount of time every time?
    3. What URL is the data gotten from?
    4. If you visit that URL (just copy/paste it into your browser location bar), you can see the actual data that is sent. Or look at the serverData global variable.

    CoffeeRun Examples

    Let's look at these examples:

    coffeerun

    Coffeerun Code

    :::JavaScript
    (function (window) {
      'use strict';
    
      var App = window.App || {};
      var $ = window.jQuery;
    
      function RemoteDataStore(url) {
        if (!url) {
          throw new Error('No remote URL supplied');
        }
    
        this.serverUrl = url;
      }
    
      RemoteDataStore.prototype.add =
      function (key, val) {
        $.post(this.serverUrl, val, function (resp) {
          console.log(resp);
        });
      };
    
      RemoteDataStore.prototype.getAll =
      function (cb) {
        $.get(this.serverUrl, function (resp) {
          console.log(resp);
          cb(resp);
        });
      };
    
      RemoteDataStore.prototype.get =
      function (key, cb) {
        $.get(this.serverUrl + '/' + key,
            function (resp) {
                console.log(resp);
               cb(resp);
            });
      };
    
      RemoteDataStore.prototype.remove =
      function (key) {
        $.ajax(this.serverUrl + '/' + key,
               { type: 'DELETE' });
      };
    
      App.RemoteDataStore = RemoteDataStore;
      window.App = App;
    })(window);
    

    Your Questions

    I'll answer your Ajax questions

    Choose

    Recall that one definition of the binomial coefficient (the N Choose K function) is a recursive function. We looked at this before, but let's remind ourselves:

    :::JavaScript
    function choose(n,k) {
        if( k==0 || k==n ) {
            return 1;
        } else {
            return choose(n-1,k-1)+choose(n-1,k);
        }
    }
    
    function test_choose(n) {
        for (var k = 0; k <= n; k++ ) {
             console.log(n+" choose "+k+" is "+choose(n,k));
        }
    }
    

    Notes:

    The base cases are k==0 and k==n. Every recursive function needs base cases and needs to always reach a base case.

    The recursive case is all others, and breaks down to the sum of two recursive calls, both of which are closer to the base cases than this call. Thus, we make progress.

    Bounds Checking plugin

    jQuery's only flaw is to ignore "errors" where the selector doesn't match anything.

    I wrote a jQuery plugin to add two methods to check.

    bounds-check

    You're welcome to use it. I did in the reference solution. It's as easy as this:

    :::JavaScript
    $("#fred").one().html('Gotcha!');
    

    If #fred doesn't exist, you'll get an error instead of nothing.

    I loaded my library into this page, so try this in the JavaScript console:

    :::JavaScript
    $("#containr").one().html();
    

    Summary

    Ajax

    • Ajax allows us to send (POST) and receive (GET) data from a server without having to leave our current page.
    • We can also use the .ajax jQuery method if we want to use other verbs like PUT or DELETE.
    • Ajax requests are asynchronous, so we must supply a callback function if we care about the response from the server (usually we will, but sometimes we might not).

    LocalStorage

    • We can save data to the browser using a generic key/value store
    • localStorage.key = value

    Recursion

    • Very important for handling recursive data structures, such as trees (including web page documents).
    • Write a function to solve the "generic" case of a single node (leaf or internal node) and trust that the recursive calls will do the right thing.