Recursion without overun - javascript

I've been looking for real world examples of recursion. Remember, programming Wizards, I'm and artist and in Photoshop scripting (scriptus modus operandi) it's normally used to loop over all layers and sub layers.
I'm working on a (simple) recursion script to solve a four digit combination lock. You know, start with 1, then try 2, then 3 etc until the solution is found. To make things easy in the example the second digit is correct so we know that we don't have to change that. Also the initial state the numbers start zero, but we know there are no zeroes in the final solution.
The attempt must match the solution AND also add up to 10 in order to
be solved.
This may seem a bit stoppid, but I want to put in a two part condition of the solution, mainly because I can then apply what I've learned and write a brute force suduko solver. But you must crawl before you can ice skate...
var puzzle = [0,2,0,0]; // source
var solution = [1,2,3,4];
var s = superCopy(puzzle); // working array
drawPuzzle(s);
solvePuzzle(s, puzzle);
var total = checkTotal(s, solution);
var solution = checkSolution(s, solution);
function checkTotal(arr, source)
{
var c = 0;
// count the total
for (var i = 0; i < arr.length; i++)
{
c += arr[i];
}
if (c == 10)
{
alert("Total OK")
return true;
}
}
function checkSolution(arr, source)
{
// check the solution
for (var i in arr)
{
if (arr[i] != source[i]) return false
return true;
}
}
function solvePuzzle(arr, source)
{
for (var i = 0; i < arr.length; i++)
{
// check the source
var sourceCell = source[i];
//alert("checking source " + sourceCell)
//if it's a zero we can change it
if (arr[i] == 0)
{
cell = arr[i];
cell+=1;
if (cell > 4) cell = 0;
arr[i] = cell;
}
}
// check the solution
for (var i in arr)
{
// overflow time!
if (arr[i] != source[i]) solvePuzzle(arr, source)
else
{
alert("All done!")
}
}
}
function drawPuzzle(arr)
{
var p = "";
var c = 0;
for (var i = 0; i < arr.length; i++)
{
if (arr[i] == 0) p += "-"
else p += arr[i];
c+=1;
}
alert(p);
}
function superCopy(arr)
{
// returns a true copy of an array
tempArr = new Array();
for (var i = 0; i < arr.length; i++)
{
if (arr[i] == 0) tempArr[i] = 1 // changed, thanks Nostradamnit!
else tempArr[i] = arr[i]
}
return tempArr
}
The script is incomplete. This what I have so far, it falls over with an overflow error. Note solvePuzzle and checkTotal functions are not called because I realised that solvePuzzle needs to call itself and work out the solution...which is when I ran into overflow problems and got a bit confused.
I realise that this type of question runs dangerously close to a "fix my code" venture, so I'm prepared to put a bounty out for it. Thanks.

There are a couple of problems with your code. First up, your checkSolution function stops at the very first number that matches. You probably want it to check every number before returning true, so you should move return true outside the for loop. It only gets there if all numbers match up.
Another flaw is the superCopy function, which as Nostradamnit pointed out has a flawed condition.
Your next problem is in solvePuzzle, where you have this:
if (arr[i] == 0)
{
cell = arr[i];
cell+=1;
if (cell > 4) cell = 0;
arr[i] = cell;
}
The thing is, because arr[i] is 0 and you only add 1, cell will never be 4. So your if is never going to fire. This is where your infinite loop is: it only increments values that are zero with one, so after all zeros became one, it never goes further and you keep checking if "1111" is the solution.
Now the overflow: you shouldn't be calling solvePuzzle for every cell, this grows exponentially:
for (var i in arr)
{
// overflow time!
if (arr[i] != source[i]) solvePuzzle(arr, source)
else
{
alert("All done!")
}
}
Also, you never check the result again, so the looping never ends. You would probably want to change this to this:
if(checkTotal(arr) && checkSolution(arr, source))
{
alert("All done!");
return;
}
solvePuzzle(arr, source);
As a side note (not a bug causing thing): your checkTotal function isn't using the source parameter, so you can probably leave that out. Also, there is a rogue variable called cell in solvePuzzle, which isn't a big deal in this case, but it would be better to put var in front of it so it doesn't become global. Also, there is a sourceCell variable that never gets used.

At first glance, it seems that your superCopy function is flawed. You create an empty array, then compare its 0 index (which doesn't exist) to the input array. Is that where you are getting your "overflow"?

The problem seems to be that you call solvePuzzle(arr, source) multiple times in the same iteration:
for (var i in arr)
{
// overflow time!
if (arr[i] != source[i]) solvePuzzle(arr, source)
else
{
alert("All done!")
}
}
So you iterate over every item in the array, and if it is not equal to the source item, you call solvePuzzle again. But if you have several items that don't match, solvePuzzle will get called multiple times on the same array. And for each call, it gets called again multiple times. Therefore your function calls grow exponentially and result finally in a stack overflow.
What you probably intended to do, is the following:
var areArraysEqual = true;
for (var i in arr)
{
if (arr[i] != source[i]) {
areArraysEqual = false;
break;
}
}
if (areArraysEqual) {
alert("All done!");
} else {
solvePuzzle(arr, source);
}

I haven't looked over your solution yet, but i did write a recursive one for myself to solve the problem. Perhaps it can be of help as a guideline?
var solution = [1,2,3,4];
function checkSolution(arr) {
return solution.every(function(item, index){ return item === arr[index]; });
}
function increment(arr) {
for (var i=0; arr[i] === 9; i++) {
arr[i] = 0;
}
arr[i]++;
return arr;
}
function solvePuzzle(arr) {
if (isNaN(arr[arr.length-1])) {
return null;
}
if (checkSolution(arr)) {
return arr;
}
return solvePuzzle(increment(arr));
}
var solution = solvePuzzle([1,1,1,1]);
console.log(solution); // [1,2,3,4]

Maybe little bit off topic but I have created the easiest recursion example possible in my opinion.
http://jsfiddle.net/33yfS/
var str = "9785";
function rec(a, b)
{
var c=0;
if(b==-1)
return "";
if(a!=str.charAt(b))
return rec(a+1, b);
else
{
return rec(0,b-1)+""+a;
}
}
var ans = rec(0,str.length-1)

Related

Explain return to me when its nested

I am pretty new to javascript and doing a course.I understand the return principle but when its nested in for example a nested for loop i get really confused.Would anyone mind to explain it to me?here is nothing nested but I got some example-code:
let generatePlayerBoard = (numberOfRows,numberOfColumns)=>{
const board = [];
for (var rowIndex = 0; rowIndex < numberOfRows.length; rowIndex++) {
const row = [];
for (var columnIndex = 0; columnIndex < numberOfColumns.length; columnIndex++) {
row.push(' ');
}
board.push(row);
}
return board;
};
console.log(generatePlayerBoard(2,3));
Thank you already for the help :D
The basics of nested returns is that a function can only return a value once, so once a function reaches its first return statement it ends the function.
Here's an example:
function hello () {
for(i = 0; i < 10; i++) {
if(i > 10) {
return 'Since i is never > 10 Im never reached so I never get to return anything'
} else {
return 'Hello!'
}
return 'The else statements returns before Im reached'
}
return 'Im never reached';
}
alert(hello());
Run that little script and you'll get an alert that says 'Hello!'
As mentioned before, in your script the return isn't nested, it's just run after all your other code has run.
Nesting is when you e.g. run:
if(1 < 2) {
if(2 < 3) {
// This second if is called a nested if because it's run inside another
// if statement
}
}
if(1 < 2 && 2 < 3) {
// This is also a form of nested if because it has the same effect as running
// the code above
}

How to check whether word within array is included in string, without regex?

I’m trying to figure out why my code is not giving the right output.
My input shouldn’t be contained within the array elements.
I found an easy way to solve it with regex, so I am not using regex for that one.
Please, break down my code, and tell me what is the problem with the code.
function checkInput(input, words) {
var arr = input.toLowerCase().split(" ");
var i, j;
var matches = 0;
for (i = 0; i < arr.length; i++) {
for (j = 0; j < words.length; j++) {
if (arr[i] != words[j]) {
matches++;
}
}
}
if (matches > 0) {
return true;
} else {
return false;
}
};
console.log(checkInput("Move an array element from one array", ["from"])); // should be false
console.log(checkInput("Move an array element from one array", ["elem"])); // should be true
if (arr[i] != words[j]) will be true at some point or another most of the time.
You want to check the opposite and return the opposite logic, so:
if(arr[i] == words[j]) {
matches++;
}
and
if (matches > 0) {
return false;
} else {
return true;
}
But a simpler way would be:
function checkInput(input, words){
let lowerCaseInput = input.toLowerCase().split(" ");
return words.find(word => lowerCaseInput.includes(word)) === undefined;
}
Array.prototype.find will return undefined iff no element is found that satisfies a specific rule provided by the callback function, word => lowerCaseInput.includes(word) in this case. So we check whether its return value is undefined which will tell us whether a word has been matched in input.
Note that your function unnecessarily checks for the entire words array even though it only matters whether one word matches.
Also, the words in words are case-sensitive! If you don’t want that, replace .includes(word) by .includes(word.toLowerCase()).
Because you are using matches > 0, you think it will return true only when no matches is found. But what happens is that when you have input ab aa cc and word aa
first iteration matches = 1
second iteration matches = 1
third iteration matches = 2
So matches will contain how many times word is different from items of input. So as result, it will always return true as long as input is more than two words long, for at least one word of input will be different from word. You can rather consider increasing the value of matches if word is found instead.
function checkInput(input, words) {
var arr = input.toLowerCase().split(" ");
var i, j;
var matches = 0;
for (i = 0; i < arr.length; i++) {
for (j = 0; j < words.length; j++) {
if (arr[i] === words[j]) {
matches++;
}
}
}
if (matches === 0) {
return true;
} else {
return false;
}
};
console.log(checkInput("Move an array element from one array", ["from"]));
console.log(checkInput("Move an array element from one array", ["elem"]));

Comparing values between two arrays

I'm trying to set up a function that checks if a word or a text is a palindrome. To do that, it splits the text so that every letter is an element of a new array, it takes rid of the white spaces and it makes the reverse array.
Then it checks if every element of the two arrays, at the same positions, are equal. If not it returns false, if yes it returns true.
Here the function:
function palindrome(str) {
var low = str.toLowerCase();
var newArray = low.split("");
var noSpace = newArray.filter(function(val) {
return val !== " ";
});
var reverse = noSpace.reverse();
function check (a, b) {
console.log(`checking '${a}' against '${b}'`);
var partial;
var result = 1;
for (var i = 0; i < a.length; i++) {
console.log(`comparing '${a[i]}' and '${b[i]}'`);
if (a[i] !== b[i]) {
result = 0;
} else {
partial = 1;
result *= partial;
}
}
return result;
}
var result = check(noSpace, reverse);
if (result == 1) {
return true;
} else {
return false;
}
}
palindrome("r y e");
I don't know what's wrong but it seems that the function keeps on returning a true value no matter what word or text I pass to the function. What is wrong with that?
Your issue seems to be because reverse() changes the actual array as well. So doing
var reverse = noSpace.reverse();
Will reverse noSpace and assign a reference to it on the variable reverse. That is, both arrays will be the same (reversed) array.
To bypass that, I've used .slice() to create a copy of the original array, and then called .reverse() on that new array, ridding you of any conflicts.
Here's a working snippet of what it looks like:
function palindrome(str) {
var str_array = str.toLowerCase().split("");
var no_space = str_array.filter(function(val) {
return val !== " ";
});
// By applying '.slice()', we create a new array
// reference which can then be reversed and assigned
// to the 'reverse' variable
var reverse = no_space.slice().reverse();
function check(a, b) {
var partial;
var result = 1;
for(var i=0; i < a.length; i++) {
if(a[i] !== b[i]) {
// We don't need to keep
// comparing the two, it
// already failed
return 0;
} else {
// I've kept this part even though
// I don't really know what it is
// intended for
partial = 1;
result *= partial;
}
}
return result;
}
return check(no_space, reverse) === 1;
}
console.log(palindrome("a b a"));
console.log(palindrome("r y e"));
The way you have coded for palindrome is way too complicated.
But there is one problem with your code: when you do a reverse() it changes the original array as well.
So you will need to make sure that you copy it via slice().
Also you can directly send a boolean result rather than doing a 1 and 0.
At result *= partial;, 1 * 1 will always equal 1
I didn't correct your code, but here is a optimized solution for you.
function palindrom(string) {
var arr = string.split("");
var lengthToCheck = Math.floor(arr.length / 2);
for (var i = 0; i < lengthToCheck; i++) {
if (arr[i] != arr[arr.length - (1 + i)]) {
return false;
}
}
return true;
}
First I split the array after every charater of the passed String. After that I get the half of the length of the array as it's enough to check just one half.
With the for-loop I compare the first half with the second half. As soon as I found two characters that do not match I return false. In case the whole first half matches the second half of the array, the for-loop will be completed and after that true will be returned.
What's actually happening is .reverse() reverses an array in place, it then stores a reference to that array which is not what you're calling in your check() method.
Simple fix would be to change your if statement:
if (a[i] !== b.reverse()[i])

Defining an array function in javascript

I was been asked in an interview to code for the following scenario.
var ele = [2,5,4,8,4,9].second();
If the above statement is written in javascript it ele should contain second highest element from that array. In this case it should contain 8.
how do you write the code for this? I assume we will have to write a function second()
but how do we call make sure it takes the array as that parameter?
Please help me with this. Thank You in advance.
That should get you started
Array.prototype.second = function() {
var biggest = -Infinity;
var second = -Infinity;
for (var i = 0; i < this.length; ++i) {
if (this[i] > biggest) {
second = biggest;
biggest = this[i];
} else if (this[i] < biggest && this[i] > second) {
second = this[i];
}
}
return second;
};
You should include error handling and there's plenty of room to optimize this solution.
Add a method to the Prototype of the Array Object.
Array.prototype.second = function() {
// Your logic here
}

loop through 'distinct' values and ignore duplicates - javascript

I'm completely rewriting this question to explain it better,
i have a list of objects as shown in the image.
These all have the property statusCode: 62467 but the journey property goes like: 0,1,2,3,3,4,4,4,4
i want to loop through these objects and return the FIRST of the duplicated objects with the same journey number.
So i want to return the bold objects: 0,1,2,3,3,4,4,4,4
$.each(points, function (index, point) {
for (i = 0; i < journeyNumber.length; i++) {
if (point.k.journey === journeyNumber[i] && point.k.statusCode === '62467') {
console.log(point);
latlngs.push(point.j.aa.k);
latlngs.push(point.j.aa.B);
}
}
});
the screenshot is the log of console.log(point), so ideally i would like another loop inside which returns only the first object of the same journey number.
hope this makes sense and thank you for your time.
You can track which numbers have already been dealt with, and, on finding duplicates of them, skip them.
var already_seen = []; //container for logging unique values
$.each(points, function(index, point) {
if (already_seen.indexOf(point) !== -1) return; //if already in container, skip
already_seen.push(point); //log unseen values in the container
for (i = 0; i < journeyNumber.length; i++) {
if (journey === journeyNumber[i] && statusCode === '62467') {
latlngs.push(lat);
latlngs.push(lng);
console.log(journey)
}
}
});
How about making use of filter
var unique = [];
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
$.each(points, function(index, point) {
for (i = 0; i < journeyNumber.length; i++) {
if (journey === journeyNumber[i] && statusCode === '62467') {
latlngs.push(lat);
latlngs.push(lng);
unique.push(journey)
}
}
console.log(unique.filter(onlyUnique));
});

Categories

Resources