I am trying to create a family tree with callback functions nested in callback functions, i'm hoping to get at least 5 generation. the function receive the person's id, which then look for everyone in the database that has the property 'father' with the same id.
This is the function for getting the children of the person
var getChildren=function (person, callback) {
keystone.list('Person').model.find().where('father', person.id).exec(function(err, children) {
callback(children);
})
}
this is how i use the callback function
function getFamilyTree(person){
getChildren(person, function(children){
person.children=children;
for (var i=0;i<person.children.length;i++) {
!function outer(i){
if (isNotEmpty(person.children[i])){
getChildren(person.children[i],function(children){
person.children[i].children=children;
for (var j=0;j<person.children[i].children.length;j++){
!function outer(j){
if (isNotEmpty(person.children[i].children[j])){
getChildren(person.children[i].children[j],function(children){
person.children[i].children[j].children=children;
for (var k=0;k<person.children[i].children[j].children.length;k++){
!function outer(k){
if (isNotEmpty(person.children[i].children[j].children[k])){
getChildren(person.children[i].children[j].children[k],function(children){
person.children[i].children[j].children[k].children=children;
})
}
}(k);
}
})
}
}(j);
}
});
}
}(i);
}
})
}
as you can see, it is very complicated. It works, but sometimes it doesn't retrieve all 5 generation but only 4 or 3, sometimes even 1 and i don't know why, please help my guys, and i'm also a new comer so please be easy with me, thanks in advance!
If you use promises instead of callbacks, you could use a recursive async function to resolve trees of arbitrary depth:
function getChildren(person) {
return new Promise((resolve, reject) => {
keystone.list('Person').model.find().where('father', person.id).exec((err, children) => {
if(err) reject(err);
else resolve(children);
});
});
}
async function getFamilyTree(person, maxDepth, depth=0) {
if(depth >= maxDepth) return person;
const children = (await getChildren(person)).filter(isNotEmpty);
person.children = await Promise.all(
children.map(child => getFamilyTree(child, maxDepth, depth + 1))
);
return person;
}
getFamilyTree({id: 'rootPersonId'}, 5)
.then(tree => console.log(tree))
.catch(error => console.log(error));
You definitely need to use recursion. I don't know exactly how your data looks, or how getChildren works, but this should point you in the right direction:
function getFamilyTree ( person ) {
getChildren( person, function( children ) {
person.children = children;
for ( var i = 0; i < person.children.length ) {
getFamilyTree( person.children[ i ] );
}
})
}
The first thing you can do to simplify the code is to pull out the outer function that you use multiple times throughout the getFamilyTree function so you don't repeat it all over the place. It'll need an update to take the child node as a parameter instead of the index for simplicity.
Since your callback is the same every time, you can pull it out into it's own function. It'll need a slight update to take in the parent item, since you are constantly referencing the person variable throughout the callback.
Your outer function can be something like:
function outer(child) {
if (isNotEmpty(child)) {
getChildren(child, getChildrenCallback);
}
}
And then the getChildrenCallback will be:
function getChildrenCallback(children, parent) {
parent.children = children;
for ( var i = 0; i < children.length; i++) {
outer(child[i]);
}
}
Related
In the following code I am trying to add decorators to a function. For certain reasons I would like to display the function attribute "name". However, I have no access to it as soon as I am in the individual functions. Also, I'm not sure why the functions are called from the bottom up. What are the reasons for all of the points mentioned and how can I avoid them?
let rectangleArea = (length, width) => {
return length * width;
}
const countParams = (fn) => {
return (...params) => {
console.log('countParams', fn.name)
if (params.length !== fn.length) {
throw new Error(`Incorrect number of parameters for ${fn.name}!`);
}
return fn(...params);
}
}
const requireIntegers = (fn) => {
return (...params) => {
console.log('requireIntegers', fn.name)
params.forEach(param => {
if (!Number.isInteger(param)) {
throw new TypeError(`Params must be integers at ${fn.name}!`); //Can't access fn.name
}
});
return fn(...params);
}
}
//Why running from bottom to top?
rectangleArea = countParams(rectangleArea);
rectangleArea = requireIntegers(rectangleArea);
console.log(rectangleArea(20, 30, "hey"));
The first time you make a decorated function for a given function, that returned function does not have a name -- it is anonymous. So when you then pass that decorated function to be decorated again, fn will be that anonymous function.
To solve this, assign the name of the fn function also to the returned decorated function. That way the name will stick even when you decorate that function again, and again...
Here is a helper function that will assign the name property to a given function:
const setName = (deco, value) => {
Object.defineProperty(deco, "name", {value, writable: false});
return deco;
}
let rectangleArea = (length, width) => {
return length * width;
}
const countParams = (fn) => {
return setName((...params) => {
console.log('countParams', fn.name)
if (params.length !== fn.length) {
throw new Error(`Incorrect number of parameters for ${fn.name}!`);
}
return fn(...params);
}, fn.name);
}
const requireIntegers = (fn) => {
return setName((...params) => {
console.log('requireIntegers', fn.name)
params.forEach(param => {
if (!Number.isInteger(param)) {
throw new TypeError(`Params must be integers at ${fn.name}!`); //Can't access fn.name
}
});
return fn(...params);
}, fn.name);
}
rectangleArea = countParams(rectangleArea);
rectangleArea = requireIntegers(rectangleArea);
console.log(rectangleArea(20, 30, "hey"));
Why the functions are called from the bottom up.
Because in your decorator the last step is to call fn.
That fn might be an already decorated function, and so it is normal that earlier decorations of the function run later.
It is like wrapping a birthday present several times, each time with a different color of wrapping paper. When your friend unpacks it, they will get to see the colors of wrapping paper in the reverse order from the order in which you had applied them.
So you want to do something additional with your decorators? They need some common behavior? We've shown we know how to do that already: with decorators. Your decorators need decorators of their own!
Here I write a decorator-decorator keep which takes a decorator function and returns a new decorator function which keeps the name and length properties of the function passed to it. (Say that five times fast!)
It uses the same technique as the answer from trincot, but is less intrusive, as you can simply wrap the decorator functions just as you would the underlying ones. Here I do that at definition time, since we never really want these decorators without this behavior, but you can do it as you like.
let rectangleArea = (length, width) => {
return length * width;
}
const keep = (decorator) => (fn) =>
Object .defineProperties (decorator (fn), {
name: {value: fn .name, writable: false},
length: {value: fn .length, writable: false}
})
const countParams = keep ((fn) => {
return (...params) => {
console.log('countParams', fn.name)
if (params.length !== fn.length) {
throw new Error(`Incorrect number of parameters for ${fn.name}!`);
}
return fn(...params);
}
})
const requireIntegers = keep ((fn) => {
return (...params) => {
console.log('requireIntegers', fn.name)
params.forEach(param => {
if (!Number.isInteger(param)) {
throw new TypeError(`Params must be integers at ${fn.name}!`); //Can't access fn.name
}
});
return fn(...params);
}
})
//Why running from bottom to top? -- answered by #balastrong
rectangleArea = countParams(rectangleArea);
rectangleArea = requireIntegers(rectangleArea);
console.log(rectangleArea(20, 30));
console.log(rectangleArea(20, 30, "hey"));
.as-console-wrapper {max-height: 100% !important; top: 0}
The name keep was originally keepName before I realized that I personally would also want the function to keep the arity intact. I couldn't come up with a clear useful name for this... which is a large warning sign to me. So there may be something still wrong with this design.
It's not from bottom to top, it's the order you set.
With your decorators, you basically just did this:
requireIntegers(countParams(rectangleArea(20, 30, "hey"))).
Which means first it executes requireIntegers by passing it as input everything else (countParams(rectangleArea(20, 30, "hey"))).
Then you see the console log and the error as params.forEach scans the params and finds 'hey' which is not a number.
I want to make this code prettier with recursion.
findModel = function(oldModel, ...modelStyles) {
let model = oldModel.elements;
let i = 0;
try {
do {
model = model.children.find(child => child.mStyle === modelStyles[i]);
i += 1;
} while (i < modelStyles.length);
return model;
} catch (e) {
return undefined;
}
};
tried this:
findModel = function(oldModel, ...modelStyles) {
let model = oldModel.elements;
let i = 0;
if (i < modelStyles.length) {
model = model.children.find(child => child.mStyle === modelStyles[i]);
i += 1;
return model;
} else {
return undefined;
}
};
but it's still not working well. in the first code I get only the element, in the second one I get also undefined.
What did I wrong?
As amply noted in comments, you are actually never calling the function recursively.
When it comes to "pretty", I would not go for recursion, but for reduce:
var findModel = function(oldModel, ...modelStyles) {
try {
return modelStyles.reduce((model, style) => model.children.find(child => child.mStyle === style), oldModel.elements);
} catch (e) {} // No need to explicitly return undefined. It is the default
};
If you really need recursion, then first realise that your function expects a first argument type that never occurs again. Only the toplevel model has an elements property, so you can only call this function for ... the top level of your hierarchy.
To make it work, you would need another function that takes the model type as it occurs in the children:
var findModel = function(oldModel, ...modelStyles) {
function recur(model, style, ...modelStyles) {
if (style === undefined) return model;
return recur(model.children.find(child => child.mStyle === style), ...modelStyles);
}
// Need to change the type of the first argument:
try {
return recur(oldModel.elements, ...modelStyles);
} catch (e) {}
};
If you would change the code where the function is called initially, you could of course pass mainmodel.elements instead of mainmodel, so that this type difference problem is resolved. If you can make that change, then the recursive function can become:
var findModel = function(model, style, ...modelStyles) {
if (style === undefined) return model;
try {
return recur(model.children.find(child => child.mStyle === style), ...modelStyles);
} catch (e) {}
};
Still, I would prefer the reduce variant.
The point of recursive function is to call themselves into themselves. In your case, you are calling the function once, but the function never call itself so it just go through once. I'm not sure of the context so I can't fix your code but i can give you an example of recursion.
Lets say we have an object with property. Some are string, some are number and some are objects. If you want to retrieve each key of this object you would need recursion, since you don't know how deep the object goes.
let objectToParse = {
id: 10,
title: 'test',
parent: {
id: 5,
title: 'parent',
someKey: 3,
parent: {
id: 1,
title: 'grand-parent',
parent: null,
someOtherkey: 43
}
}
};
function parseParentKey(object) {
let returnedKey = [];
let ObjectKeys = Object.keys(object);
for(let i = 0; i < ObjectKeys.length; i++) {
if(typeof object[ObjectKeys[i]] === "object" && object[ObjectKeys[i]] !== null) {
// we are calling the methode inside itself because
//the current property is an object.
returnedKey = returnedKey.concat(parseParentKey(object[ObjectKeys[i]]));
}
returnedKey.push(ObjectKeys[i]);
}
return returnedKey;
}
console.log(parseParentKey(objectToParse));
I know this does not answer your question but it gives you a hint on how to use recursion properly. If your first code works, I don't see why you would need to change it in the first place.
I have a little problem with my filter function. I have a data structure as shown in the image below.
As you can see, I have an array of objects named bridals and it keeps another array of objects named plans. So inside that I am trying to filter people.
Here is the filter function.
export function peopleFilter(bridals, people){
if (people.length === 0) {
return bridals;
} else {
bridals.forEach(bridal => {
bridal.plans.filter((item) => {
if (item.people === people) {
return bridals;
}
})
})
return bridals;
}
}
The function peopleFilter() should filter plans with selected value only and return with bridals, but it returns nothing. No error is shown as well.
So I tried something like below.
bridals.forEach(bridal => {
bridal.plans.slice().reverse().forEach((item, index, object) => {
if (item.people !== people) {
bridal.plans.splice(object.length - 1 - index, 1)
}
})
})
return bridals;
This above code is doing what I want. But there is one problem. So in the end when I select no value, it should display all plans. But it doesn't because I already removed the plans using splice() every time I select some value.
So I am stuck about this. How can I fix it?
In this case it might be easier to iterate over your bridal array with a map. Within the map, filter plans for each bridal, then return a new obj, which can be accomplished with spread syntax.
const filterFunc = (bridals, people) => {
return bridals.map(bridal => {
const filteredPlans = bridal.plans.filter(plan => plan.people === people);
return { ...bridal, plans: filteredPlans };
})
}
Let's say you want to know the people count for all bridals and plans:
var total = 0;
bridals.forEach(function(b){
b.plans.forEach(function(o){
total += o.people;
});
});
Of course, I can't really tell what you're trying to do. I could write an entire API for this.
You need to return a boolean value inside the filter() instead of return bridals;. Additionally, this returned array needs to be reassigned to bridal array as well.
export function peopleFilter(bridals, people) {
if (people.length === 0) {
return bridals;
} else {
bridals.forEach(bridal => {
bridal = bridal.plans.filter((item) => {
return item.people === people;
})
})
return bridals;
}
}
I understand basic recursion, but this problem has be stumped. I have a tree structure set up in a database, where each node(row) has an id and parent id.
I need a function that can run and in the callback return an array of all the descendants of a particular node given its id.
I've been able to put together a function that can print out all of the values, but I can't figure out how to capture them and return them in the callback. I know the base case isn't set up correctly, as I'm not sure what it should even be.
I'd appreciate any help! Thank you!
// My "database"
var nodes_collection = [
{id:"id1",name:"name1",parentid:"."},
{id:"id2",name:"name2",parentid:"id1"},
{id:"id3",name:"name3",parentid:"id1"},
{id:"id4",name:"name4",parentid:"id2"},
{id:"id5",name:"name5",parentid:"id3"},
{id:"id6",name:"name6",parentid:"id3"},
{id:"id7",name:"name7",parentid:"id5"},
{id:"id8",name:"name8",parentid:"id7"},
{id:"id9",name:"name9",parentid:"id7"},
{id:"id10",name:"name10",parentid:"id9"},
];
// This is NOT a real function, it simply performs the function that the real getChildren does when connected to my database!!!
function getChildren(parentid, callback){
var children = [];
for(var i=0; i < nodes_collection.length; i++){
if(nodes_collection[i].parentid == parentid){
children.push(nodes_collection[i].id);
}
}
callback(children);
}
function allDescendants(parentid, callback) {
getChildren(parentid, function(childNodes) {
if (false) { // Only false because I don't know what my base case should be.
//console.log("done");
} else {
for (var i = 0; i < childNodes.length; i++) {
var child = childNodes[i];
allDescendants(child);
console.log(child); // Here it prints out all the values. How can I capture them? and return them with my callback?
}
}
});
}
allDescendants("id3", function(result){
console.log(result);
});
EDIT:
Due to some confusion, I've changed the code to a bare bones example of what I'm trying to do that can be run locally !!! getChildren() is NOT a real function, it simply performs the function that the real getChildren does when connected to my database!!!
Bottom line:
The code in question works to recursively touch all values. Now how can I store all the values that are currently being outputted via console.log()?
Here's one simple way. We create a result object and an intermediary recursive function, keeping allDescendants as a wrapper. When the recursion is complete, we return the result that now has all the descendants.
JsvaScript code:
// My "database"
var nodes_collection = [
{id:"id1",name:"name1",parentid:"."},
{id:"id2",name:"name2",parentid:"id1"},
{id:"id3",name:"name3",parentid:"id1"},
{id:"id4",name:"name4",parentid:"id2"},
{id:"id5",name:"name5",parentid:"id3"},
{id:"id6",name:"name6",parentid:"id3"},
{id:"id7",name:"name7",parentid:"id5"},
{id:"id8",name:"name8",parentid:"id7"},
{id:"id9",name:"name9",parentid:"id7"},
{id:"id10",name:"name10",parentid:"id9"},
];
// This is NOT a real function, it simply performs the function that the real getChildren does when connected to my database!!!
function getChildren(parentid, callback){
var children = [];
for(var i=0; i < nodes_collection.length; i++){
if(nodes_collection[i].parentid == parentid){
children.push(nodes_collection[i].id);
}
}
callback(children);
}
function allDescendants(parentid, callback) {
let result = [];
let go = function(children){
for (child of children){
result.push(child);
getChildren(child, go)
}
}
getChildren(parentid, go);
callback(result);
}
allDescendants("id3", function(result){
console.log('result: ' + JSON.stringify(result));
});
I propose this:
let obj = [
{id:"id1",name:"name1",parentid:"."},
{id:"id2",name:"name2",parentid:"id1"},
{id:"id3",name:"name3",parentid:"id1"},
{id:"id4",name:"name4",parentid:"id2"},
{id:"id5",name:"name5",parentid:"id3"},
]
function getChilds(obj, parent_id, callback) {
if(obj.length === 0) return;
else if (typeof callback !== 'function'){
throw new Error("The callback must be a function ");
return;
}
let childs = obj.filter(function (c) {return c.parentid == parent_id })
if(childs.length > 0 ){
childs = childs.map(function (c) {return c.id})
}
callback(childs)
}
// Test
getChilds(obj, "id1", function (childs) {console.log(childs)})
I have two arrays, called 'objects' and 'appliedObjects'. I'm trying to come up with an elegant way in Javascript and/or Angular to move objects from one array to another.
Initially I did something like this:
$scope.remove = function () {
angular.forEach($scope.appliedObjects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.objects.push(element);
$scope.appliedObjects.splice(index, 1);
}
});
}
$scope.add= function () {
angular.forEach($scope.objects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.appliedObjects.push(element);
$scope.objects.splice(index, 1);
}
});
}
But then I realized that when the value was removed from the looping array, and it would not add or remove every other item, since it went by index.
Then I tried using a temporary array to hold the list of items to be added or removed, and I started getting strange referential issues.
I'm starting to spin a bit on what the best solution to this problem would be...any help and/or guidance would much appreciated.
function moveElements(source, target, moveCheck) {
for (var i = 0; i < source.length; i++) {
var element = source[i];
if (moveCheck(element)) {
source.splice(i, 1);
target.push(element);
i--;
}
}
}
function selectionMoveCheck(element) {
if (element.selected) {
element.selected = false;
return true;
}
}
$scope.remove = function () {
moveElements($scope.appliedObjects, $scope.objects, selectionMoveCheck);
}
$scope.add = function () {
moveElements($scope.objects, $scope.appliedObjects, selectionMoveCheck);
}
When a construct does too much automatically (like forEach, or even a for-loop, in this case), use a more primitive construct that allows you to say what should happen clearly, without need to work around the construct. Using a while loop, you can express what needs to happen without resorting to backing up or otherwise applying workarounds:
function moveSelected(src, dest) {
var i = 0;
while ( i < src.length ) {
var item = src[i];
if (item.selected) {
src.splice(i,1);
dest.push(item);
}
else i++;
}
}
You are altering the array while iterating on it, you will always miss some elements.
One way of doing it would be to use a third array to store the references of the objects that need to be removed from the array:
// "$scope.add" case
var objectsToRemove = [];
$scope.objects.forEach(function (value) {
if (value.selected) {
value.selected = false;
$scope.appliedObjects.push(value);
objectsToRemove.push(value);
}
});
objectsToRemove.forEach(function (value) {
$scope.objects.splice($scope.objects.indexOf(value), 1);
});
If you wish to move simply whole array you could do:
appliedObjects = objects;
objects = []
Of course it won't work if they were parameters of a function!
Otherwise I cannot see other way than copying in the loop, e.g.
while (objects.length) {
appliedObjects.push(objects[0]);
objects.splice(0,1);
}
or if you like short code :) :
while (objects.length) appliedObjects.push(objects.splice(0,1));
check fiddle http://jsfiddle.net/060ywajm/
Now this maybe is not a fair answer, but if you notice you are doing alot of complicated object/array manipulations, you should really check out lodash or underscore library. then you could solve this with on liner:
//lodash remove function
appliedObjects.push.apply( appliedObjects, _.remove(objects, { 'selected': true}));
//or if you want to insert in the beginning of the list:
appliedObjects.splice(0, 0, _.remove(objects, { 'selected': true}));
This is a first pass at what I think will work for you. I'm in the process of making a test page so that I can test the accuracy of the work and will update the tweaked result, which hopefully there will not be.
EDIT: I ran it and it seems to do what you are wanting if I understand the problem correctly. There were a couple of syntax errors that I edited out.
Here's the plunk with the condensed, cleaned code http://plnkr.co/edit/K7XuMu?p=preview
HTML
<button ng-click="transferArrays(objects, appliedObjects)">Add</button>
<button ng-click="transferArrays(appliedObjects, objects)">Remove</button>
JS
$scope.transferArrays = function (arrayFrom, arrayTo) {
var selectedElements;
selectedElements = [];
angular.forEach(arrayFrom, function(element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function(element) {
arrayTo.push(arrayFrom.splice(
arrayFrom.map(function(x) {
return x.uniqueId;
})
.indexOf(element.uniqueId), 1));
});
};
Old code
$scope.remove = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.appliedObjects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.objects.push($scope.appliedObjects.splice(
$scope.appliedObjects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
$scope.add = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.objects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.appliedObjects.push($scope.objects.splice(
$scope.objects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
You can use this oneliner as many times as many items you need to move from arr1 to arr2 just prepare check func
arr2.push(arr1.splice(arr1.findIndex(arr1El => check(arr1El)),1)[0])
You can use this to concat 2 arrays:
let array3 = [...array1, ...array2];