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¶
- Exercise from last time
- Discuss A9, 20 Questions
- Briefly about Cloning and Event delegation
- Answer your recursion questions
- Explore Recursion
- Recap Ajax
- 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
- You'll have to manage and work with trees
- You'll have to dynamically create pairs of clickable buttons
- You'll have to have a recursive function to show the tree
- 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:
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:
Here's a differently drawn figure:
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?
- What happens if we return zero for a leaf instead of 1?
- What happens if, on non-leaves, we return zero plus the sum of the sizes of the children?
- What happens if, for leaves, we return the leaf instead of 1?
- 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?
- Since leaves don't count, the function ends up counting just the number of non-leaves. We could call this function `notleafcount`
- Since we don't count 1 for each non-leaf, the function ends up counting just the leaves. We could call this function `leafcount`
- 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.
- 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.
Timing¶
Which message is printed first?
:::JavaScript
$.get('characters.json',function (data) {
console.log('Done. Got '+data);
});
console.log('Finally done.');
Demo¶
Answer the following questions:
- How long does it take for the data to arrive?
- Does it take the same amount of time every time?
- What URL is the data gotten from?
- 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 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.
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.