why loop inside recursive function not complete in javascript - javascript

I need to :
check every node in tree start from the root, loop on its node ,check each node if there any child related to that node and call the same function again (recursive function).
My code is:
var tree = new Array();
tree[0] = ["Node1", "Node2", "Node3", "Node4"]
tree[1] = ["Node1", "Node2"]
tree[2] = ["Node1", "Node2", "Node4"]
function schTree(treeArr){
//LOOP ON CHILD NODE TO CHECK IF THE NODE IS PARANT FOR SOME OTHER NODE OR NOT and do some actions
for(i=0;i<treeArr.length; i++){
if(treeArr[i].length){
schTree(treeArr[i]);
};
}
}
//call search tree function
schTree(tree);
The problem is :
The loop did not complete if I recall the function.
I think that each calling for the function recursively It construct new function above the current function (work on the same place in memory not creating new function)
How to :
Make normal recursive function ?????
Thanks in advance
Maged Rawash

You only received the first node because you declared your for loop without the keyword var
for(i=0;i<treeArr.length; i++)
By the time the first nodes array was traversed, i was equal to 3
Here are my comments on the rest of your code
var tree = [];
tree[0] = ["Node1", "Node2", "Node3", "Node4"]
tree[1] = ["Node5", "Node6"]
tree[2] = ["Node7", "Node8", "Node9"]
//recursive function that checks if a node
function schTree(treeArr){
//FIX A
//find out if element is an array
if(Array.isArray(treeArr)){
//FIX B
//You did not declare i with var, meaning each recursive call
//carried the prior value of i with it.
//so after iterating over the first node, i = 3
//which is greater than the length of node 1 and 2
for(var i=0;i<treeArr.length; i++){
//FIX C
//removed inner length check, not needed
//schTree checks for array before getting to this
//point
schTree(treeArr[i]);
}
}
else {
//While you'd probably want to do a clause for
//objects, we'll assume only arrays here.
//FIX D
//Do something with the non array element
console.log(treeArr);
}
}
schTree(tree);

The way you're checking it isn't going to work because letters have a .length of 1, so you're going to get pass in tree, tree[0], tree[0][0], and then infinitely pass in tree[0][0][0] until the stack gets too big and you get a RangeError. Instead, do this:
function schTree(treeArr){
for (var i = 0; i < treeArr.length; i++){
if (treeArr[i].constructor === String && treeArr[i].length > 1) schTree(treeArr[i]);
}
}

Related

Removing A Function from an Array

I have an array of functions that I'm trying to use like a delegate event in c#.
I push a function to the array, and when the array loops over the functions it will get called.
Problem is I'm having trouble removing the function once I'm done with it, which kind of defeats the purpose.
Here is my code. I'm totally up for a different method of doing this, I'm fairly new to JS/JQ so this is what I came up with.
var MouseMoveFunctions = [];
$(document).ready(function(){
//Create the event to call our functions.
$(document).mousemove(function(e){
CallMouseMoveFunctions(e);
});
});
function CallMouseMoveFunctions(e){
//Only continue if there is atleast 1 function in the array.
if(MouseMoveFunctions.length == 0) return;
//Call each function in the array.
for(var i = 0; i < MouseMoveFunctions.length;i++){
MouseMoveFunctions[i](e);
}
}
$(document).ready(function(){
//Add the TrackMouse function to the array.
MouseMoveFunctions.push(function(event){TrackMouse(event)});
});
var mX = 0;
var mY = 0;
function TrackMouse(e){
mX = e.pageX;
mY = e.pageY;
var index = MouseMoveFunctions.indexOf(function(event){TrackMouse(event)});
alert(index); //Always coming up -1, so it isn't getting removed
//Try and remove the function if it exists, just for now so I know its working
if(index != -1){
MouseMoveFunctions.splice(index);
}
}
//Always coming up -1, so it isn't getting removed
You always get -1 because you're passing a unique function object to .indexOf(), so it can't already be in the array. The anonymous function that you pushed into the array has no other reference, so you can't remove it by reference.
Because you're pushing and removing a function that simply passes along the event argument, you can instead push the function itself.
MouseMoveFunctions.push(TrackMouse);
Then you'll be able to successfully find it if you look for the same function by identity.
var index = MouseMoveFunctions.indexOf(TrackMouse);
Note that if you put the same function into the array more than once, you'll need to remove it separately for each time.
Also, as noted by Scott, you need to provide the number of items to remove.
A better solution would be to use a Set instead of an Array. Also, you can get rid of those .ready() handlers.
var MouseMoveFunctions = new Set();
//Create the event to call our functions.
$(document).mousemove(CallMouseMoveFunctions);
function CallMouseMoveFunctions(e){
//Only continue if there is atleast 1 function in the array.
if(MouseMoveFunctions.size == 0) return;
//Call each function in the set.
for(const fn of MouseMoveFunctions) {
fn(e);
}
}
//Add the TrackMouse function to the array.
MouseMoveFunctions.add(TrackMouse);
var mX = 0;
var mY = 0;
function TrackMouse(e){
mX = e.pageX;
mY = e.pageY;
MouseMoveFunctions.delete(TrackMouse);
}
You need to pass a 1 as the second argument to splice to tell it to remove 1 element at the index position you provided as the first argument.
.splice(index, 1)

Reimplementing getElementsByClassName using Recursion - Javascript

I'm currently working on learning recursion and am trying to reimplement the getElementsByClassName function by walking the DOM using recursion. I finally feel like I've grasped the concepts but I'm having issues when I push the matching elements into a results array. Heres my code:
var getElementsByClassName = function(className){
var results = [];
var domWalker = function(node) {
for (var i=0; i<node.children.length; i++) {
if (node.children[i].classList.contains(className)) {
console.log(node.children[i])
results.push(node.children[i])
}
if (node.children[i].children.length > 0) {
domWalker(node.children[i])
}
}
return results;
};
domWalker(document.body);
console.log(results)
};
Basically, I need the results array to hold the matching elements it finds in HTML format like so:
[<div class="title"></div>, <button class="click"></button>]
... yet when I push these elements into my results array they change to the: [div.title, button.click] format.
I added the console.log statement above the call to results.push to see if the results appear in the proper format before they are pushed to the array which they do. The results being pushed to the array are the results I'm looking for, they just appear in the wrong format.
Why is push causing the format of my results to change and how can I get around this issue?
I solved this problem once upon a time. Although I haven't read through your solution, here is mine, heavily commented. I hope it helps:
var getElementsByClassName = function(className, node){
// The empty results array, which gets created whenever the function is
// called.
var results = [];
// Default the node to the document's body if it isn't set. This way, we can
// call the function recursively with child elements, but never have to
// worry about it the first time around.
node = node || document.body;
// If the node contains the class list in question, let's push it into the
// results array.
if (node.classList && node.classList.contains(className)) {
results.push(node);
}
// Now, let's fetch the child nodes of the current node.
var children = node.childNodes;
// If child nodes exist, then we proceed here.
if (children) {
// Let's now loop over all child nodes of the current node in question. This
// way, we'll be able to perform checks on each child node.
for (var i = 0; i < children.length; i++) {
// Fetch the i child node.
var child = children[i];
// At this point, we want to pass the child node back into the function,
// implementing recursion. The same checks above will occur, and if the
// node has the class name, it will be added to the results array from
// that function call.
//
// This returns an array, and we need to merge it with the current set
// of results, so we concat it.
//
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
results = results.concat(getElementsByClassName(className, child));
}
}
// We then return the combined results array from all the recursive function
// calls!
return results;
};
node.children[i] holds a reference to the HTML element
console.log() applies an implicit .toString() method giving what you see.
you need this additional code (to be extended to all possible tagNames you find):
var el = node.children[i];
el = '<' + el.tagName + (el.className ? ' class="' + el.className + '"': '') + '/>';
console.log(el);
results.push(el);

JavaScript Higher Order Function loop/recursion/confusion

Implement a function that takes a function as its first argument, a number num as its second argument, then executes the passed in function num times.
function repeat(operation, num) {
var num_array = new Array(num);
for(var i = 0; i < num_array.length; i++){
return operation(num);
}
}
//
// The next lines are from a CLI, I did not make it.
//
// Do not remove the line below
module.exports = repeat
RESULTS:
ACTUAL EXPECTED
------ --------
"Called function 1 times." "Called function 1 times."
"" != "Called function 2 times."
null != ""
# FAIL
Why doesn't this work?
I am assuming that I am starting a function called repeat. Repeat has two parameters and takes two arguments.
For the loop I create an array which has a length which is equal to the num passed in.
I then start a for loop, setting a counter variable i to 0. Then I set a conditional which states that i should always be less than the length of the num_array which was created earlier. Then the counter i is incremented up by one using the ++.
For every time that the conditional is true, we should return the value of calling running the function operation and passing the num as an argument.
The last two lines allow for easy running of the program through command line with pre programmed arguments being used.
Thank you for your time!
The return statement is breaking out of the function on the first iteration of the loop. You need to remove the return, and just call the function like this:
function repeat(operation, num) {
for(var i = 0; i < num; i++){
operation(num);
}
}
Note that I have removed the creation and iteration of the array, you do not need it for what you are doing here.
Also your initial question does not specify that you need to pass num to the function (but you do list it in your steps below), so you may be able to just do operation() instead of operation(num).
You probably want something like the below, rather than returning the result of the function operation(num) you want to store the value in teh array. return in a loop breaks out of the loop, so it would always only run once..
function repeat(operation, num) {
var num_array = new Array(num);
for(var i = 0; i < num_array.length; i++){
num_array[i] = operation(num);
}
}
//
// The next lines are from a CLI, I did not make it.
//
// Do not remove the line below
module.exports = repeat
If you are asking why the loop is not running, it's because you have to run the function after defining it (I'm assuming you are not already calling the function somewhere else).
Once calling the function repeat you will see that it is exiting after one iteration. This is because you are returning the operation - returning causes the function to end. To stay in the loop you just need to call operation(), without the return.
Also you don't need to create an array, you can just use the counter you are defining in the for loop.
So the code would look something like this:
var op = function(arg) {console.log(arg);},
n = 5;
function repeat(operation, num) {
for(var i = 0; i < num; i++){
operation(i);
}
}
repeat(op ,n);
// The next lines are from a CLI, I did not make it.
//
// Do not remove the line below
module.exports = repeat

change array passed to function

I pass 2 arrays to a function and want to move a specific entry from one array to another. The moveDatum function itself uses underscorejs' methods reject and filter. My Problem is, the original arrays are not changed, as if I was passing the arrays as value and not as reference. The specific entry is correctly moved, but as I said, the effect is only local. What do I have to change, to have the original arrays change as well?
Call the function:
this.moveDatum(sourceArr, targetArr, id)
Function itself:
function moveDatum(srcDS, trgDS, id) {
var ds = _(srcDS).filter(function(el) {
return el.uid === uid;
});
srcDS = _(srcDS).reject(function(el) {
return el.uid === uid;
});
trgDS.push(ds[0]);
return this;
}
Thanks for the help
As mentioned in the comments, you're assigning srcDS to reference a new array returned by .reject(), and thus losing the reference to the array originally passed in from outside the function.
You need to perform your array operations directly on the original array, perhaps something like this:
function moveDatum(srcDS, trgDS, id) {
var ds;
for (var i = srcDS.length - 1; i >= 0; i--) {
if (srcDS[i].uid === id) {
ds = srcDS[i];
srcDS.splice(i,1);
}
}
trgDS.push(ds);
return this;
}
I've set up the loop to go backwards so that you don't have to worry about the loop index i getting out of sync when .splice() removes items from the array. The backwards loop also means ds ends up referencing the first element in srcDS that matches, which is what I assume you intend since your original code had trgDS.push(ds[0]).
If you happen to know that the array will only ever contain exactly one match then of course it doesn't matter if you go forwards or backwards, and you can add a break inside the if since there's no point continuing the loop once you have a match.
(Also I think you had a typo, you were testing === uid instead of === id.)
Copy over every match before deleting it using methods which modify Arrays, e.g. splice.
function moveDatum(srcDS, trgDS, id) { // you pass an `id`, not `uid`?
var i;
for (i = 0; i < srcDS.length; ++i) {
if (srcDS[i].uid === uid) {
trgDS.push(srcDS[i]);
srcDS.splice(i, 1);
// optionally break here for just the first
i--; // remember; decrement `i` because we need to re-check the same
// index now that the length has changed
}
}
return this;
}

Create an array with tree elements in Javascript

I need to create an array from tree elements in Javascript and being a newbie I don't know how to achieve this.
pseudo-code :
function make_array_of_tree_node(tree_node)
{
for (var i = 0; i < tree_node.childCount; i ++) {
var node = tree_node_node.getChild(i);
if (node.type ==0) {
// Here I'd like to put a link (node.title) in an array as an element
} else if (node.type ==6) {
// Here the element is a folder so a I need to browse it
make_array_of_tree_node(node)
}
}
}
// Some code
make_array_of_tree_node(rootNode);
// Here I'd like to have access to the array containing all the elements node.title
You can declare an array like this:
var nodes = [];
Then you can add things to it with:
nodes.push(something);
That adds to the end of the array; in that sense it's kind-of like a list. You can access elements by numeric indexes, starting with zero. The length of the array is maintained for you:
var len = nodes.length;
What you'll probably want to do is make the array another parameter of your function.
edit — To illustrate the pattern, if you've got a recursive function:
function recursive(data, array) {
if ( timeToStop ) {
array.push( data.whatever );
}
else {
recursive(data.subData, array);
}
}
Then you can use a second function to be the real API that other code will use:
function actual(data) {
var array = [];
recursive(data, array); // fills up the array
return array;
}
In JavaScript, furthermore, it's common to place the "recursive" function inside the "actual" function, which makes the recursive part private and keeps the global namespace cleaner.

Categories