My introductory tutorial has suddenly become very advanced. I have no idea how this program works. Can you explain in plain language?
At the end it prints (((1 * 3) + 5) * 3), but I don't get it at all. I understand that findSequence gets passed 24, that triggers function find. I'm assuming that function find gets passed 1,"1" with the latter being assigned to history?? but I don`t understand why the second 1 is in quotation marks "1!, nor do I understand the use of quotation marks when it returns find(start etc.
function findSequence(goal) {
function find(start, history) {
if (start == goal)
return history;
else if (start > goal)
return null;
else
return find(start + 5, "(" + history + " + 5)") ||
find(start * 3, "(" + history + " * 3)");
}
return find(1, "1");
}
print(findSequence(24));
You have an "output" string (((1 * 3) + 5) * 3), a goal 24 and a first character 1. The 1 is the 1 in 1 * 3. This program will assemble a string containing a math expression, adding (), *3 and +5 to try to obtain the goal. The two parameters of the find function are the current total and the expression that will generate the total. Clearly the expression is a string. The find compares the current total to the goal, and if it's equal then the expression is correct and it returns it, if the current total is > of the goal then he failed and return null. Otherwhise he add some operations to the expression. He tries two "ways", one multiplying * 3 the current result, the other adding +5 to the current result. It's recursive on a tree (so each time he will bifurcate in two recursive calls). null || something == something, so the branch that will find a response will return his response, the other branch will return null and the "winning" response will be passed back.
Let's say the goal is 11.
find(1, "1")
compares 1 with 11 and calls: (2.) find(1 + 5, "(" + "1" + " + 5)") (so find(6, "(1 + 5)") and (3.) find(1 * 3, "(" + "1" + " * 3)") (so find(3, "(1 * 3)")
compares 6 with 11 and calls (4.) find (6 + 5, "(" + "(1 + 5)" + " + 5)") (so finds(11, "((1 + 5) + 5)") and (5.) find (6 * 3, "(" + "(1 + 5)" + " * 3)" (so find(18, "((1 + 5) * 3)"
compares 3 with 11 and calls (6.) find (3 + 5, "(" + "(1 * 3)" + " + 5)") (so finds(8, "((1 * 3) + 5)") and (7.) find (3 * 3, "(" + "(1 * 3)" + " * 3)" (so find(8, "((1 + 3) * 3)"
compares 11 with 11. The numbers are equal. So he returns "((1 + 5) + 5)" (the history). 5, 6, 7 will at a certain point go "overboard" and surpass the 11, so they'll return null. null || null == null, "((1 + 5) + 5)" || null == "((1 + 5) + 5)", so the history will win against the nulls and it will be returned.
To make it even clearer, try these versions:
function findSequence(goal) {
function find(start, history) {
if (start == goal)
return history;
else if (start > goal)
return null;
else {
var ret = find(start + 5, "(" + history + " + 5)");
if (ret == null)
ret = find(start * 3, "(" + history + " * 3)");
return ret;
}
}
return find(1, "1");
}
print(findSequence(24));
And this, where instead of an expression you'll get only as tring of + and *
function findSequence(goal) {
function find(start, history) {
if (start == goal)
return history;
else if (start > goal)
return null;
else {
var ret = find(start + 5, history + "+");
if (ret == null)
ret = find(start * 3, history + "*");
return ret;
}
}
return find(1, "1");
}
print(findSequence(24));
And be aware that, as an example, it's quite complex because it used closures (locally defined functions).
Try this tutorial out
http://nicksjavascript.blogspot.com
its made for beginners
Related
I am working my way through the javascript course on freecodecamp and im confused with the lesson on recursive functions.
I am having difficulty understanding the following code:
function sum(arr, n) {
if(n<=0) {
return 0;
} else {
return sum(arr, n-1) + arr[n-1];
}
}
sum([10,20,30,40], 3);
The specific part im struggling with is this:
arr[n-1];
would the return line not be returning sum([10,20,30,40], 3-1) + arr[3-1] resulting in 30+30 = 60?
Any help with this would be greatly appreciated. Or even pointing me in the right direction to look into this further.
Thanks
Let's write the original code in a more intuitive way by putting arr[n-1] first. This way we can keep expanding each call to sum() to the right.
But first let's note down what sum(arr, n) call will return for each n
if n > 0 => arr[n-1] + sum(arr, n-1)
if n == 0 => 0
n == 3 => arr[2] + sum(arr, 2)
n == 2 => arr[1] + sum(arr, 1)
n == 1 => arr[0] + sum(arr, 0)
n == 0 => 0
Now we expand our steps:
sum(arr, 3)
== arr[2] + sum(arr, 2) // expand sum(arr,2) where n = 2
== arr[2] + arr[1] + sum(arr, 1) // expand sum(arr,1)
== arr[2] + arr[1] + arr[0] + sum(arr,0) // expand sum(arr,0)
== arr[2] + arr[1] + arr[0] + 0
== 30 + 20 + 10 + 0
Test with
function sum(arr, n) {
console.log(`calling sum(arr, ${n})`);
if(n<=0) {
console.log(`returning 0`);
return 0;
} else {
console.log(`calculating [sum(arr, ${n-1}) + ${arr[n-1]}]`);
let s = sum(arr, n-1);;
console.log(`returning [sum(arr, ${n-1}) + ${arr[n-1]}] = [${s} + ${arr[n-1]}]`);
return s + arr[n-1];
}
}
sum([10,20,30,40], 3);
The output will be:
calling sum(arr, 3)
calculating [sum(arr, 2) + 30]
calling sum(arr, 2)
calculating [sum(arr, 1) + 20]
calling sum(arr, 1)
calculating [sum(arr, 0) + 10]
calling sum(arr, 0)
returning 0
returning [sum(arr, 0) + 10] = [0 + 10]
returning [sum(arr, 1) + 20] = [10 + 20]
returning [sum(arr, 2) + 30] = [30 + 30]
Two other classic examples of simple recursive functions are factorial and fibonacci, because those two formulas itself are recursive. Multiplication could also be computed recursively, if you think as a * b being a + (a + ...) where a is added b times.
If you're trying to code those functions, there's a hint to code this last example:
5 * 10 is equal to 5 + 5 * 9, which is equal to 5 + 5 + 5 * 8 and so on.
sum([10,20,30,40], 3-1) Will call sum function again, think about it.
Recursive functions usually operate with a value that is immediately accesible and another value that they obtain by calling themselves until a base case is reached.
In this example, that accesible parameter is one of the numbers of an array and the operation is just a simple addition. The base case will be reached when the function's parameters satisfy the if condition.
Think of the different iterations that happen here to understand how the final result is gotten:
First iteration, sum([10,20,30,40], 3)
The if condition is not fulfilled because 3 (n) is greater than 0, so the else branch's code is executed.
We have sum([10,20,30,40], 2) + arr[2]. We don't know the result of the recursive call yet but we have the value of the number located in the third position of the array, that is 30 (arrays are usually considered to start from 0).
Second iteration, sum([10,20,30,40], 2)
Again, this is not the base case yet (if branch), so we have:
sum([10,20,30,40], 2-1) + arr[2-1] ->
sum([10,20,30,40], 1) + arr[1] ->
sum([10,20,30,40], 1) + 20
Third iteration, sum([10,20,30,40], 1)
At this point, we have 30 and 20 as "partial results". These numbers and the results of the remaining iterations will all be added up, because that's what the code in the else branch does, an addition.
sum([10,20,30,40], 1-1) + arr[1-1] ->
sum([10,20,30,40], 0) + arr[0] ->
sum([10,20,30,40], 0) + 10
Another partial result has been added: 10.
Forth and final iteration, sum([10,20,30,40], 0)
The base case is finally reached, the if branch's code is executed and the recursion stops right here because there isn't another call to the recursive function in this code. The result of sum([10,20,30,40], 0) is 0 because the code returns 0.
Now that we have reached the end of the recursion, we can retrace our steps.
• The result of the third iteration is sum([10,20,30,40], 0) + 10. We know this is 0 + 10 now, so 10.
• The result of the second iteration is sum([10,20,30,40], 1) + 20 = 10 + 20 = 30.
• And the result of the first call and the original call to the function is sum([10,20,30,40], 2) + 30 = 30 + 30 = 60.
Recursive functions are really tricky at first but once you understand this logic of partial results that are "accumulated" until the moment the base case is reached, they get easier. Just take your time.
'use strict';
let apples = '3';
let bananas = '4';
console.log(+apples + (apples = +bananas + 3));
The output is 10, unexpectedly. I thought it would be 14, and the compiler would think something like this
console.log(+apples + (apples = +bananas + 3));
console.log(+apples + (apples = 4 + 3));
console.log(+apples + (apples = 7)); //the variable 'apples' is going to be 7
console.log(+apples + 7); //'apples' now equals to 7
console.log(7 + 7);
console.log(14)
14
But on the step 4, 'apples' apparently equals to 3. Why isn't the output 14?
update: Can it be that there are parentheses around each operand, which are automatically added even though not directly written?
console.log((+apples) + ((apples = (+bananas) + (3)))); //since parentheses now have equal precedence(order), actions are done from left to right
console.log(3 + (apples = 4 + 3));
console.log(3 + (apples = 7)); //the variable 'apples' is going to be 7
console.log(3 + 7); //'apples' now equals to 7
console.log(3 + 7);
console.log(10)
10
That would, I think, logically explain why there is 10 instead of 14.
Sorry for clumsy code. I was just doing some practice after reading about operators in js.
On step 4, value of apples isn't 7 because the expression in your code example is evaluated from left to right.
So the following expression:
+apples + (apples = +bananas + 3)
is evaluated as:
Coerce the value of apples to a number
3 + (apples = +bananas + 3)
Coerce the value of bananas to a number
3 + (apples = 4 + 3)
Add 4 + 3 and assign the result of addition to apples
3 + (apples = 7)
(apples = 7) - value of this expression is 7
3 + 7
Final result = 10.
I am not good at algorithm analysis. The source code is from this place: https://repl.it/KREy/4
Instead of dynamic programming, this piece of code uses a cache to optimize the BigO by sacrificing memory. However, I just don't know how to calculate the BigO mathematically after this cache mechanism is added. May anyone genius give an explanation?
To ease reading I will copy and paste them in the following space:
// using cache to optimize the solution 1 from http://www.techiedelight.com/longest-palindromic-subsequence-using-dynamic-programming/
const cache = {};
var runningtime = 0;
var runningtimeWithCache = 0;
function computeGetLP(x, start, end){
const answer = a => {
runningtime++;
return a;
}
console.log("try to compute: " + x + " " + start + " " + end + " ");
if(start > end)
return answer(0);
if(start == end)
return answer(1);
if(x[start] == x[end])
return answer(2 + computeGetLP(x, start+1, end-1));
return answer(Math.max(computeGetLP(x, start+1, end),
computeGetLP(x, start, end-1)));
}
function computeGetLPWithCache(x, start, end){
const answer = a => {
runningtimeWithCache ++;
console.log("do cache: " + x + " " + start + " " + end + " is " + a);
cache["" + x + start + end] = a;
return a;
}
console.log("try to compute: " + x + " " + start + " " + end + " ");
if(cache["" + x + start + end]){
console.log("hit cache " + x + " " + start + " " + end + " "+ ": ",cache["" + x + start + end]);
return cache["" + x + start + end];
}
if(start > end)
return answer(0);
if(start == end)
return answer(1);
if(x[start] == x[end])
return answer(2 + computeGetLPWithCache(x, start+1, end-1));
return answer(Math.max(computeGetLPWithCache(x, start+1, end),
computeGetLPWithCache(x, start, end-1)));
}
const findLongestPadlindrom1 = s => computeGetLPWithCache(s, 0, s.length-1)
const findLongestPadlindrom2 = s => computeGetLP(s, 0, s.length-1)
const log = (function(){
var lg = [];
var output = function(text){
lg.push(text);
}
output.getRecord = function(){
return lg;
}
return output;
})();
log("Now let's do it with cache")
log("result: "+findLongestPadlindrom1("ABBDCACB"))
log("running time is: " + runningtimeWithCache)
log("Now let's do it without cache")
log("result: "+findLongestPadlindrom2("ABBDCACB"))
log("running time is: " + runningtime)
log.getRecord();
I'm not an expert in algorithms either, but I remember cache techniques like this from Introduction to Algorithms, chapter 15, just beside Dynamic Programming. It has the same big O to DP, which is O(n^2) in your case.
Each call to computeGetLPWithCache() costs O(1) for it does not contain loops. Consider the worst case where x[start] != x[end] in each recursion. How many times are we going to call computeGetLPWithCache()?
Let n = length(x), [start, end] represent a call to computeGetLPWithCache(x, start, end), and F(n) equals the number of calls. In computeGetLPWithCache(x, 0, n), 2 sub calls - [0, n-1] and [1, n] - are issued. The former costs F(n), and when we're doing the latter, we discover that in each iteration, the first call's [start, end] range is a true subset of [0, n-1] whose result is already written to cache during the [0, n-1] call, thus no need for recursing. Only the second call which has the element n in it has to be calculated; there're n such calls [1,n][2,n][3,n]...[n,n] (one in each stack layer), so F(n+1) = F(n) + O(n).
F(n) = F(n-1) + O(n-1) = F(n-2) + O(n-2) + O(n-1) = ... = O(1+2+...+(n-1)) = O(n^2).
Hope I've got the meaning through. Replies are welcome.
I came across this example of a recursive function in a textbook. I can see there are other threads on this site that ask a question about this very example from Eloquent JavaScript but that user's difficulty in understanding the example was to do with not understanding how functions are invoked in general.
function findSolution(target){
function find(start, history){
if(start == target)
return history
else if(start > target)
return null
else
return find(start + 5, "(" + history + " + 5)") ||
find(start * 3, "(" + history + " * 3)")
}
return find(1, "1");
}
You supply a target number to the findSolution function, and it executes the inner function, find, which calls itself until it finds a way to reach the target number by adding 5 or multiplying by 3. Say I supply the number 13 as a target to reach. The parameters increment as follows.
Start = 1 + 5, History = "(1 + 5)"
Start = 6 + 5, History = "(1 + 5) + 5"
Start = 11 + 5, History = "(1 + 5) + 5) + 5"
Start becomes 16, and this is higher than 13, so this will return null. So the find function tries to multiply start by 3 instead of adding 5, going back to 11...
Start = 11 * 3, History = "(1 + 5) + 5) * 3"
This is also too high, and will so return null. Surely the function stops here, right??
When I test it out though, console logging the start values each time find recursively calls itself, it continues to call itself until it reaches number 13. The start values the function cycles through after 33 are:
18
3
8
13
But how is any of this possible? Shouldn't the function just return false after it reaches 16 and 33?
Console.log history just before the base case. That will help to understand how it works
There is a syntax error in your (the) example code. It should appear like this:
function findSolution(target){
function find(start, history){
if(start == target)
return history
else if(start > target)
return null
else
return (find(start + 5, "(" + history + " + 5)") ||
find(start * 3, "(" + history + " * 3)"))
}
return find(1, "1");
}
(Note the structure of the last return in the inner function.)
When running the updated function with an input of 13, I see this in the console:
(((1 * 3) + 5) + 5)
the question is pretty similar to this thread Javascript..totally lost in this tutorial.
function findSequence(goal) {
function find(start, history) {
if (start == goal)
return history;
else if (start > goal)
return null;
else
return find(start + 5, "(" + history + " + 5)") ||
find(start * 3, "(" + history + " * 3)");
}
return find(1, "1");
}
print(findSequence(24));
I got stuck at this part :
find(start * 3, "(" + history + " * 3)");
each time start goes beyond goal what does it do? it says it return null but when I test and put breakpoint on
if (start == goal) it shoes this on the console
history: "(((((1 + 5) + 5) + 5) + 5) + 5)"
start: 26
history: "(((((1 + 5) + 5) + 5) + 5) * 3)"
start: 63
it add up *3 and take off +5, I don't understand how.
The return statement:
return find(start + 5, "(" + history + " + 5)") ||
find(start * 3, "(" + history + " * 3)");
is an expression involving the "||" operator. That operator will cause the left-hand side to be evaluated. If the result of that is not null, zero, false, or the empty string, then that value will be returned. If it is one of those "falsy" values, then the second expression is evaluated and returned.
In other words, that could be re-written like this:
var plusFive = find(start + 5, "(" + history + " + 5)");
if (plusFive !== null)
return plusFive;
return find(start * 3, "(" + history + " * 3)")
If "start" ever exceeds "goal", the function returns null. Of course, if both the alternatives don't work, then the whole thing will return null.
The expression:
find(start + 5, "(" + history + " + 5)") ||
find(start * 3, "(" + history + " * 3)")
Will first attempt to evaluate:
find(start + 5, "(" + history + " + 5)")
If the returned value is not null, 0, false, or the empty string then the statement evaluates to the returned value.
If the returned value is null, 0, false, or the empty string then the following will be evaluated next:
find(start * 3, "(" + history + " * 3)")
If the returned value is not null, 0, false, or the empty string, then the statement evaluates to the returned value.
If the returned value is null, 0, false, or the empty string, then the statement evaluates to null, 0, false, or the empty string (whichever was returned by the *3 function call).
So the line:
return find(start + 5, "(" + history + " + 5)") ||
find(start * 3, "(" + history + " * 3)")
is like saying "I'm going to try to find the solution by guessing that I add 5 at this step, and if that doesn't work I'll try to multiply by 3 at this step, and if that doesn't work I give up!"