What is wrong with this javascript closure? - javascript

I have a recursive function (exploreNode)that updates the value of a variable (branch_queue) that is declared right above it.
When I run normally (without a closure function), it works as expected.
When I place inside of a closure function, the recursive function doesn't iterated through children nodes the way its supposed to. it remains on the same initial node, until a "Max Call Stack" error fires.
The purpose of the recursive function is to explore a JSON tree, until a desired ID is found. As it traverses through the tree, the branch_queue var is updated with the roadmap to the node of interest.
The closure was to not have the branch_queue as a global function.
I tried both in es6 and es5, thinking it could be a problem with scope and using "const" and "let".
The examples are below.
I also have the code block below that worked without the closure.
Tree that I feed in as a parameter
let u = [
{
id: 0,
label: 'l0',
children: [
{
id: 1,
label: 'l1'
},
{
id: 2,
label: 'l2',
children: [
{
id: 3,
label: 'l3'
},
{
id: 4,
label: 'l4'
},
{
id: 5,
label: 'l5'
},
{
id: 6,
label: 'l6',
children: [
{
id: 7,
label: 'l7'
},
{
id: 8,
label: 'l8'
},
{
id: 9,
label: 'l9'
},
{
id: 10,
label: 'l10'
}
]
}
]
}
]
}
]
WHAT DID WORK
let branch_queue = [];
// Assumes that the ID exists!
const exploreNode = (nodeIdOfInterest, nodeTree) => {
// var branch_queue = [];
for (let i = 0; i < nodeTree.length; i++) {
const nodeToCheck = nodeTree[i];
if (nodeToCheck.id == nodeIdOfInterest) {
branch_queue.push(nodeToCheck.id);
return nodeToCheck.label;
} else if(nodeToCheck.children) {
branch_queue.push(nodeToCheck.id);
return exploreNode(nodeIdOfInterest, nodeToCheck.children);
}
}
}
exploreNode(3, contentTree);
console.log(branch_queue); // prints the correct roadmap
WHAT DOESN'T WORK
ES5
function fn (nodeIdOfInterest, nodeTree) {
let branch_queue = [];
console.log('here');
// Assumes that the ID exists!
function exploreNode () {
var branch_queue = [];
console.log('in here');
for (var i = 0; i < nodeTree.length; i++) {
var nodeToCheck = nodeTree[i];
console.log(`${nodeToCheck.label} : ${nodeToCheck.id}`);
if (nodeToCheck.id == nodeIdOfInterest) {
console.log('found it');
branch_queue.push(nodeToCheck.id);
return nodeToCheck.label;
} else if(nodeToCheck.children) {
console.log('checking children');
branch_queue.push(nodeToCheck.id);
return exploreNode(nodeIdOfInterest, nodeToCheck.children);
}
}
};
exploreNode();
return branch_queue;
}
console.log(fn(3, contentTree)); // throws call stack error
ES6
const fn = (nodeIdOfInterest, nodeTree) => {
let branch_queue = [];
console.log('here');
// Assumes that the ID exists!
const exploreNode = () => {
// var branch_queue = [];
console.log('in here');
for (let i = 0; i < nodeTree.length; i++) {
let nodeToCheck = nodeTree[i];
console.log(`${nodeToCheck.label} : ${nodeToCheck.id}`);
if (nodeToCheck.id == nodeIdOfInterest) {
branch_queue.push(nodeToCheck.id);
return nodeToCheck.label;
} else if(nodeToCheck.children) {
branch_queue.push(nodeToCheck.id);
return exploreNode(nodeIdOfInterest, nodeToCheck.children);
}
}
};
exploreNode();
return branch_queue;
};
console.log(fn(3, contentTree)); // throws call stack error
Expected output => [0 2 3]
Actual => . Max call stack error
The recursive function never moves beyond the very first level, and repeats indefinitely.

nodeTree in your recursive version of exploreNode is always the same starting point, the one passed into fn. Every call to exploreNode in that version starts fresh: Your calls to exploreNode are passing arguments, but it's ignoring them. The "what did work" version isn't ignoring the arguments passed to it, so it works.

Related

i wanna return correctly children's object. how can i?

function Ha8(arr, id) {
let result = [];
for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i].children)) {
// if it is a array, it going to be run recursive
result.push(arr[i].children)
const col = Ha8(result[i], id);
if(col === id) {
// find it in array in array
return result
// then return the id object,
} else {
continue; // still can't find.. go ahead!
}
} else if (arr[i]['id']===id) {
return arr[i] // will return valid id object
}
return null // if its none , return null, or parameter id is undefined.
}
}
I m write Intended direction. but its not work..
how can i fix ? give me some tip please.
let input = [
{
id: 1,
name: 'johnny',
},
{
id: 2,
name: 'ingi',
children: [
{
id: 3,
name: 'johnson',
},
{
id: 5,
name: 'steve',
children: [
{
id: 6,
name: 'lisa',
},
],
},
{
id: 11,
},
],
},
{
id: '13',
},
];
output = Ha8(input, 5);
console.log(output); // --> { id: 5, name: 'steve', children: [{ id: 6, name: 'lisa' }] }
output = Ha8(input, 99);
console.log(output); // --> null
I wanna return like that, but only return 'null' ..
need to check children's id and return children's object by using recursive.
so i write like that. but i have no idea..
how to return correctly children id's element?
I will give you an answer using a totally different approach, and using the magic of the JSON.stringify() method, more specifically the replacer optional parameter, which allows the use of a callback function that can be used as a filter.
As you can see, it simplifies a lot the final code. It could also be modified to introduce not only an id, but also any key or value, as I did in my final approach.
EDIT: Following your suggestion, as you prefer your function to be recursive, I recommend you to use the Array.reduce() method. It allows an elegant iteration through all the properties until the needs are met.
Using null as initial value, which is the last argument of the reduce method, it allows to iterate through all fields in the array in the following way:
The first if will always be skipped on the first iteration, as the initial value is null.
The second if will set the currentValue to the accumulator if the property id exists and is equal to the value you are trying to find
The third if, which you could add an Array.isArray() to add a type validation, will check if the property children exists. As it is the last one, it will only work if all the other conditions aren't met. If this property exists, it will call again Ha8Recursive in order to start again the process.
Finally, if neither of this works, it should return null. The absence of this last condition would return undefined if the input id doesn't exist
const Ha8 = (array, inputKey, inputValue) => {
let children = null;
JSON.stringify(array, (key, value) => {
if (value[inputKey] && value[inputKey] === inputValue) {
children = value;
}
return value;
});
return children;
};
const Ha8Recursive = (array, inputKey, inputValue) => {
return array.reduce((accumulator, currentValue) => {
if (accumulator) {
return accumulator;
} else if (currentValue[inputKey] && currentValue[inputKey] === inputValue) {
return currentValue;
} else if (currentValue.children) {
return Ha8Recursive(currentValue.children, inputKey, inputValue);
} else {
return null;
}
}, null)
}
const input = [{"id":1,"name":"johnny"},{"id":2,"name":"ingi","children":[{"id":3,"name":"johnson"},{"id":5,"name":"steve","children":[{"id":6,"name":"lisa"}]},{"id":11}]},{"id":"13"}];
console.log('JSON stringify function');
console.log(Ha8(input, 'id', 5));
console.log('Recursive function')
console.log(Ha8Recursive(input, 'id', 5));

Class method returns undefined bound/unbound JavaScript

Thank you in advance for any help.
Why does the return statement from return this.visited inside the if(this.queue.length === 0) code block return undefined? Whereas both console.logs before the return statement return the correct values.
What I tried: I read through a few related post here and then tried binding this to the this.traverse method. I reviewed the syntax, logic, checked for typos several times.
const Tree = require('../Tree/treeExport')
const Queue = require('../Stack_Queue/queueExport')
class BFS extends Tree {
constructor(queue,queue1) {
super()
this.visited = queue
this.queue = queue1
this.traverse = this.traverse.bind(this)
}
traverse() {
let current;
if(this.visited.length < 1) {
this.queue.enqueue(this.root)
}
if(this.queue.length > 0) {
current = this.queue.dequeue()
}
if(current.left) {
this.queue.enqueue(current.left)
}
if(current.right) {
this.queue.enqueue(current.right)
}
if(current) {
this.visited.enqueue(current.value)
}
if(this.queue.length === 0) {
console.log(this.visited, '\n \n')
console.log(this.mapToArray(this.visited), '\n \n')
return this.visited;
} else {
this.traverse()
}
}
mapToArray(queue) {
const array = []
let temp;
while(queue.length > 0) {
temp = queue.dequeue()
array.push(temp)
}
temp = null;
return array;
}
}
let queue = new Queue
let visited = new Queue
const bfs = new BFS(visited,queue)
bfs.insert(5)
bfs.insert(3)
bfs.insert(7)
bfs.insert(2)
bfs.insert(4)
bfs.insert(6)
bfs.insert(9)
console.log(bfs.traverse())
result from console:
Queue {
first: Node { value: 5, next: Node { value: 3, next: [Node] } },
last: Node { value: 9, next: null },
length: 7
}
[
5, 3, 7, 2,
4, 6, 9
]
undefined

How to return an object from an array in JavaScript [duplicate]

This question already has answers here:
Return matching objects from array of objects
(4 answers)
Closed 3 years ago.
Create a function that takes an array of people objects and returns the first found astronaut object from the array.
This is the code that I have created;
function findFirstAstronaut(people) {
for (let i = 0; i < people.length; i++) {
if (people.astronaut[i] === false) {
return null
}
else if (people.astronaut[i]) {
return people[i]
}
}
My code is run against this test;
describe("findFirstAstronaut", () => {
it("returns null if no Astronaut is in the array", () => {
expect(findFirstAstronaut([])).to.be.null;
});
it("returns a person object who is an astronaut", () => {
const astronaut = { name: "Tim Peake", isAstronaut: true };
expect(findFirstAstronaut([astronaut])).to.have.keys([
"name",
"isAstronaut"
]);
expect(findFirstAstronaut([astronaut]).isAstronaut).to.be.true;
});
it("returns the first astronaut from the array", () => {
const astronauts = [
{ name: "Johnny Karate", isAstronaut: false },
{ name: "Neil Armstrong", isAstronaut: true },
{ name: "Valentina Tereshkova", isAstronaut: true },
{ name: "Bert Macklin", isAstronaut: false },
{ name: "Eileen Collins", isAstronaut: true },
{ name: "Kip Hackman", isAstronaut: false }
];
expect(findFirstAstronaut(astronauts)).to.eql({
name: "Neil Armstrong",
isAstronaut: true
});
});
});
How do I fix my code?
ES6 introduces a new way to achieve this, if ES6 is an option for you:
myArray.find(item => {
return item.isAstronaut
})
Or even more abbreviated:
myArray.find(item => item.isAstronaut)
find() is a one of the new iterators, along with filter() and map() and others for more easily working with arrays. find() will return the first item in your array that matches the condition. The => or "arrow function" means that you do not need to explicitly include the return statement.
Read more about ES6 iterators.
You need to use the index for the array.
people[i] // for the object
people[i].isAstronaut // for a property of the object
Then you need only a check if isAstronaut is true and return with the item.
At the end outside of the for loop, return null, for a not found astronaut.
If you check inside the loop, you will return too early with the wrong result.
function findFirstAstronaut(people) {
for (let i = 0; i < people.length; i++) {
if (people[i].isAstronaut) {
return people[i];
}
}
return null;
}
One liner
arr.filter(item => item.isAstronaut)[0]
You could simply filter out array elements that have an isAstronaut property equal to false using Array.prototype.filter. I prefer filter to Array.prototype.find since ES6 isn't supported in every browser.
Once you have the filtered array simply take the element in the 0 index position. Something like below:
const astronauts = [{
name: "Johnny Karate",
isAstronaut: false
},
{
name: "Neil Armstrong",
isAstronaut: true
},
{
name: "Valentina Tereshkova",
isAstronaut: true
},
{
name: "Bert Macklin",
isAstronaut: false
},
{
name: "Eileen Collins",
isAstronaut: true
},
{
name: "Kip Hackman",
isAstronaut: false
}
];
//Array destructuring to get the first astronaut:
var [firstAstronautFound] = astronauts.filter(el => el.isAstronaut);
//Line above is same as doing this:
//var firstAstronautFound = astronauts.filter(el => el.isAstronaut)[0];
console.log(firstAstronautFound.name);
First, you may need to check whether there is any item in array or else return null.
Second, you have to check for the property
Third, you have to check for the value of the property isAstronaut
function findFirstAstronaut(people) {
if (people.length > 0)
{
for (let i = 0; i < people.length; i++) {
if (("name" in people[i]) && ("isAstronaut" in people[i])) {
if (people[i].isAstronaut)
return people[i];
else
return true;
}
}
}
else
return null;
}

Search whole javascript object with children

I currently have this object:
var obj = {
1: {
title: 'test',
children: {
2: {
title: 'test2',
children: {}
},
3: {
title: 'test3',
children: {}
}
}
}
};
The whole idea is I make a function to add an item to this object. As parameter I send the parent.
Now, I was wondering how I would get the right item object. For example if I send parent '2', it would get 2: from the children of 1:. The only way I can think of is a for loop, but I don't know if there's a more efficient way. The children can be extended even more, so a parent has children, those children have children endlessly. That's the whole idea at least.
I think with a few items a for loop is okay, but I think if I have over 50 items it's already slow, and it'll even be slower with more.
This solution use Object.keys() for getting all keys of the given object and an array iteration with short ciruit Array.prototype.some() looks for the key. If found the reference is returned, otherwise the item is checked for an object. If so the object reference is taken for a new search with getReference().
var obj = { 1: { title: 'test', children: { 2: { title: 'test2', children: {} }, 3: { title: 'test3', children: {} } } } };
function getReference(o, p) {
var r;
Object.keys(o).some(function (k) {
if (k === p) {
r = o[k];
return true;
}
if (typeof o[k] === 'object') {
r = getReference(o[k], p);
return !!r;
}
});
return r;
}
var x = getReference(obj, '2');
document.write(x.title);
If you want adding to be fast, you can preserve indexes of your child nodes in object or map (ES6). It could look like this:
function Tree() {
this.data = {};
this.indexes = {0: this.data};
}
Tree.prototype = {
addNode: function(parentIndex, index, node) {
// handle cases when parentIndex does not exist
// handle cases when index already exists
this.indexes[index] = node;
var parent = this.indexes[parentIndex];
parent.children = parent.children || {};
parent.children[index] = node;
}
}
var tree = new Tree();
tree.addNode(0, 1, { title: 'test' });
tree.addNode(1, 2, { title: 'test2' });
tree.addNode(1, 3, { title: 'test3' });
console.log(tree.data);

Build a JSON object from absolute filepaths

I receive (in my angularjs application) from a server a list of directories like this:
['.trash-user',
'cats',
'cats/css',
'cats/images/blog',
'cats/images/gallery']
And I would like to build a javascript variable which looks like this:
[{
label: '.trash-user'},
{label: 'cats',
children: [{
label: 'css'},
{label: 'images',
children: [{
label: 'blog'},
{label: 'gallery'}
]}
]}
}]
The paths are in random order.
Hope somebody has some really elegant solution, but any solution is appreciated!
Edit:
Here is my naive approach, I have real trouble with recursion.
I could only make level 0 to work:
var generateTree = function(filetree){
console.log('--------- filetree -------');
var model = [];
var paths = [];
for(var i=0;i<filetree.length;i++) {
paths = filetree[i].split('/');
for(var j=0;j<paths.length;++j) {
var property = false;
for(var k=0;k<model.length;++k) {
if (model[k].hasOwnProperty('label') &&
model[k].label === paths[0]) {
property = true;
}
}
if (!property) {
model.push({label: paths[0]});
}
}
}
console.log(model);
};
If you want an elegant solution, lets start with a more elegant output:
{
'.trash-user': {},
'cats': {
'css': {},
'images': {
'blog': {},
'gallery': {},
},
},
}
Objects are much better than arrays for storing unique keys and much faster too (order 1 instead of order n). To get the above output, do:
var obj = {};
src.forEach(p => p.split('/').reduce((o,name) => o[name] = o[name] || {}, obj));
or in pre-ES6 JavaScript:
var obj = {};
src.forEach(function(p) {
return p.split('/').reduce(function(o,name) {
return o[name] = o[name] || {};
}, obj);
});
Now you have a natural object tree which can easily be mapped to anything you want. For your desired output, do:
var convert = obj => Object.keys(obj).map(key => Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key]) } : { label: key });
var arr = convert(obj);
or in pre-ES6 JavaScript:
function convert(obj) {
return Object.keys(obj).map(function(key) {
return Object.keys(obj[key]).length?
{ label: key, children: convert(obj[key])} : { label: key };
});
}
var arr = convert(obj);
I'll venture that generating the natural tree first and then converting to the array will scale better than any algorithm working on arrays directly, because of the faster look-up and the natural impedance match between objects and file trees.
JSFiddles: ES6 (e.g. Firefox), non-ES6.
Something like this should work:
function pathsToObject(paths) {
var result = [ ];
// Iterate through the original list, spliting up each path
// and passing it to our recursive processing function
paths.forEach(function(path) {
path = path.split('/');
buildFromSegments(result, path);
});
return result;
// Processes each path recursively, one segment at a time
function buildFromSegments(scope, pathSegments) {
// Remove the first segment from the path
var current = pathSegments.shift();
// See if that segment already exists in the current scope
var found = findInScope(scope, current);
// If we did not find a match, create the new object for
// this path segment
if (! found) {
scope.push(found = {
label: current
});
}
// If there are still path segments left, we need to create
// a children array (if we haven't already) and recurse further
if (pathSegments.length) {
found.children = found.children || [ ];
buildFromSegments(found.children, pathSegments);
}
}
// Attempts to find a ptah segment in the current scope
function findInScope(scope, find) {
for (var i = 0; i < scope.length; i++) {
if (scope[i].label === find) {
return scope[i];
}
}
}
}

Categories

Resources