I am trying to understand how to recursively append a branching structure to an object.
I am trying to append children to an empty JSON object that when built out should look like the following.
nodeStructure: {
text: { name: "Parent node" },
children: [
{
text: { name: "First child" },
children: [
{
text: {name: "Grandchild"}
}
]
},
{
text: { name: "Second child" }
}
]
}
Here is the most succinct version of that code.
trackFraud = async (fraudID) => {
var root = chart_config.nodeStructure = newNode(fraudID);
await fraudClimb(root, 1);
var my_chart = new Treant(chart_config);
function newNode(node) { return {text:{name:"fraud " + node}}; }
async function fraudClimb(root, fraudID) {
var frauds = await findFraudByFromID.call(this, fraudID); // returns an array of "integers"
if (frauds.length == 0) return;
var children = root.children = [];
for (var i = 0; i < frauds.length; i++) {
children.push(newNode(frauds[i]));
fraudClimb(children[i], frauds[i]);
}
}
}
Now I am trying to wrap my head around how to traverse, or in this case append to, a structure that alternates every other level between arrays and objects
I guess the real question is how to pass an object around recursively and append to that original object.
I see two issues in your code:
The first call to fraudClimb ignores the fraudID parameter that is available. Instead of:
await fraudClimb(root, 1);
I think you need:
await fraudClimb(root, fraudID);
The recursive call to fraudClimb is not awaited, yet you need the asynchronous operation to complete before you go on. So change this:
fraudClimb(children[i], frauds[i]);
to:
await fraudClimb(children[i], frauds[i]);
Related
I have a nested data structure, and I want to create a recursive function that, given an object's name parameter, will return the parent object's name paramter.
There are several related questions, however, the answers don't explain why my function getParentName isn't working.
Why is getParentName not working?
const nestedData = {
name: "parent",
children: [{ name: "child", children: [{ name: "grandchild" }] }],
};
function getParentName(nested, name) {
if (nested.children && nested.children.map((d) => d.name).includes(name)) {
return nested.name;
} else if (nested.children) {
nested.children.forEach((child) => {
return getParentName(child, name);
});
}
return undefined; //if not found
}
//The parent of "grandchild" is "child" - but the function returns undefined
const parentName = getParentName(nestedData, "grandchild");
Why does this function not find the parent?
The problem with your answer is .forEach ignores the return value. There is no return for your else if branch. .forEach is for side effects only. Consider using a generator which makes it easier to express your solution -
function* getParentName({ name, children = [] }, query) {
for (const child of children)
if (child.name === query)
yield name
else
yield *getParentName(child, query)
}
const data = {
name: "parent",
children: [{ name: "child", children: [{ name: "grandchild" }] }],
}
const [result1] = getParentName(data, "grandchild")
const [result2] = getParentName(data, "foobar")
const [result3] = getParentName(data, "parent")
console.log("result1", result1)
console.log("result2", result2)
console.log("result3", result3)
The answer will be undefined if no matching node is found or if a matched node does not have a parent -
result1 child
result2 undefined
result3 undefined
Notice [] is needed to capture a single result. This is because generators can return 1 or more values. If you do not like this syntax, you can write a generic first function which gets only the first value out of generator -
function first(it) {
for (const v of it)
return v
}
const result1 = first(getParentName(data, "grandchild"))
const result2 = first(getParentName(data, "foobar"))
const result3 = first(getParentName(data, "parent"))
The advantages of this approach are numerous. Your attempt uses .map and .includes both of which iterate through children completely. In the other branch, .forEach is used which exhaustively iterates through all children as well. This approach avoids unnecessary .map and .includes but also stops immediately after the first value is read.
#Mulan answered my question, stating that the function failed because return statements inside .forEach() are ignored. They then offered a generator function as a superior alternative.
For the sake of clarity of comparison, here is a minimally altered form of the original function that works. forEach() was replaced with a (for x of array) loop. In addition only truthy values are returned.
function getParentName(nested, name) {
if (nested.children && nested.children.some((d) => d.name === id)) {
return nested.name;
} else if (nested.children) {
for (const child of node.children) {
const result = getParentName(child, id);
if (result) return result;
}
}
return undefined; //if not found
}
I am trying to read through a large JSONL, maybe couple hundreds up to thousands or possibly million line, below is sample of of the data.
{"id":"gid://shopify/Product/1921569226808"}
{"id":"gid://shopify/ProductVariant/19435458986040","__parentId":"gid://shopify/Product/1921569226808"}
{"id":"gid://shopify/Product/1921569259576"}
{"id":"gid://shopify/ProductVariant/19435459018808","__parentId":"gid://shopify/Product/1921569259576"}
{"id":"gid://shopify/Product/1921569292344"}
{"id":"gid://shopify/ProductVariant/19435459051576","__parentId":"gid://shopify/Product/1921569292344"}
{"id":"gid://shopify/Product/1921569325112"}
{"id":"gid://shopify/ProductVariant/19435459084344","__parentId":"gid://shopify/Product/1921569325112"}
{"id":"gid://shopify/Product/1921569357880"}
{"id":"gid://shopify/ProductVariant/19435459117112","__parentId":"gid://shopify/Product/1921569357880"}
{"id":"gid://shopify/ProductVariant/19435458986123","__parentId":"gid://shopify/Product/1921569226808"}
So each line is json object, either its a Product, or a Product Child identified by __parentId, given that the data may contain thousands of lines, what's the best way to read through it and return a regular JSON object, like this.
{
"id": "gid://shopify/Product/1921569226808",
"childrens": {
{"id":"gid://shopify//ProductImage//20771195224224","__parentId":"gid:////shopify//Product//1921569226808"},
{"id":"gid:////shopify//ProductImage//20771195344224","__parentId":"gid:////shopify//Product//1921569226808"}
{"id":"gid:////shopify//ProductImage//20771329344224","__parentId":"gid:////shopify//Product//1921569226808"}
}
}
The data is coming back from Shopify and they advice to:
Because nested connections are no longer nested in the response data
structure, the results contain the __parentId field, which is a
reference to an object's parent. This field doesn’t exist in the API
schema, so you can't explicitly query it. It's included automatically
in bulk operation result.
Read the JSONL file in reverse Reading the JSONL file in reverse makes
it easier to group child nodes and avoids missing any that appear
after the parent node. For example, while collecting variants, there
won't be more variants further up the file when you come to the
product that the variants belong to. After you download the JSONL
file, read it in reverse, and then parse it so that any child nodes
are tracked before the parent node is discovered.
You can look for look here to read more about all of thisenter link description here.
Consider using streams so that you don't have to load the entire file in memory.
You can use readline (a native module) to process each line individually.
I took the line processing part from #terrymorse https://stackoverflow.com/a/65484413/14793527
const readline = require('readline');
const fs = require('fs');
let res = {};
function processLine(line) {
const {id, __parentId} = line;
// if there is no `__parentId`, this is a parent
if (typeof __parentId === 'undefined') {
res[line.id] = {
id,
childrens: []
};
return res;
}
// this is a child, create its parent if necessary
if (typeof res[__parentId] === 'undefined') {
res[__parentId] = {
id: __parentId,
childrens: []
}
}
// add child to parent's children
res[__parentId].childrens.push(line);
return res;
}
const readInterface = readline.createInterface({
input: fs.createReadStream('large.jsonl'),
output: process.stdout,
console: false
});
readInterface.on('line', processLine);
readInterface.on('close', function() {
const resultArray = Object.values(res);
console.log(resultArray);
});
Here's a technique that:
forms an object with properties of the parent ids
converts that object to an array
(input lines converted to an array for simplicity)
const lines = [
{ "id": "gid://shopify/Product/1921569226808" },
{ "id": "gid://shopify/ProductVariant/19435458986040", "__parentId": "gid://shopify/Product/1921569226808" },
{ "id": "gid://shopify/Product/1921569259576" },
{ "id": "gid://shopify/ProductVariant/19435459018808", "__parentId": "gid://shopify/Product/1921569259576" },
{ "id": "gid://shopify/Product/1921569292344" },
{ "id": "gid://shopify/ProductVariant/19435459051576", "__parentId": "gid://shopify/Product/1921569292344" },
{ "id": "gid://shopify/Product/1921569325112" },
{ "id": "gid://shopify/ProductVariant/19435459084344", "__parentId": "gid://shopify/Product/1921569325112" },
{ "id": "gid://shopify/Product/1921569357880" },
{ "id": "gid://shopify/ProductVariant/19435459117112", "__parentId": "gid://shopify/Product/1921569357880" },
{ "id": "gid://shopify/ProductVariant/19435458986123", "__parentId": "gid://shopify/Product/1921569226808" }
];
// form object keyed to parent ids
const result = lines.reduce((res, line) => {
const {id, __parentId} = line;
// if there is no `__parentId`, this is a parent
if (typeof __parentId === 'undefined') {
res[id] = {
id,
childrens: []
};
return res;
}
// this is a child, create its parent if necessary
if (typeof res[__parentId] === 'undefined') {
res[__parentId] = {
id: __parentId,
childrens: []
}
}
// add child to parent's children
res[__parentId].childrens.push(line);
return res;
}, {});
// convert object to array
const resultArray = Object.values(result);
const pre = document.querySelector('pre');
pre.innerText = 'resultArray: ' + JSON.stringify(resultArray, null, 2);
<pre></pre>
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 3 years ago.
I am trying to build a tree structure by traversing a given ID and attaching its child to the tree. It always assigns user.children = 'sample' instead of fetched user children from user.children = usrChild, also tried usrArr[index].children = 'sample'
What I am trying to implement:
using a userID, I will fetch its children, now for each children i will fetch their children until there are no children left.
function UserProfile.getUserChildren returns a promise with data consisting of all the children.
Now, we iterate each children and fetch their children
Output Expected Visually:
Programatically What I am expecting:
[
{
text: {
userID: 1
name: 'Mike'
},
children:[
{
text: {
userID: 2
name: 'John'
},
children [
{
text: {
userID: 4
name: 'Hero'
},
children []
}
]
},
{
text: {
userID: 3
name: 'Kelvin'
},
children []
}
]
}
]
Code in Node JS:
let UserProfile = require('./UserProfile');
// Love Dad 83273010
let userID = 51405009;
var allDistributors = Array();
rootUser = new Object();
rootUser.text = {userID:userID,name:'Root'};
rootUser.children = [];
function getUserTree(userID){
return new Promise( (resolve,reject) => {
/*
* UserDownline Return User child Array
* Format:
* [
* { text: {
* userID: 45
* name: 'Mike'
* },
* children:[]
* }
* ]
*
*/
UserProfile.getUserChildren(userID).then(async (data) => {
if(data.length > 0){
rootUser.children = data;
/*
* Iterating each user to fetch and assign its child
*/
await rootUser.children.forEach(async (user,index,usrArr) => {
user.children = 'sample'
await getUserTree(user.text.title).then( async(usrChild) => {
/*
Assigning child to root user
*/
usrArr[index].children = usrChild; // STILL NOT ABLE TO ASSIGN VALUE and return user.children as 'sample'
}).then(resolve(rootUser));
});
//resolve(rootUser)
//console.log(rootUser)
//return Promise.all(rootUser);
}else
resolve([]); // return empty child when no child exist
});
//return rootUser.children;
//console.log(rootUser);
});
}
//console.log(JSON.stringify(rootUser));
getUserTree(userID).then((data) => {
console.log(JSON.stringify(data));
});
As Jaromanda X pointed out, rootUser.children.foreach returns undefined instead of a promise. Consider using rootUser.children.map instead. That returns an array of Promises that you can await on using Promise.all, which returns a promise of an array. Since map does not modify the original array, you would need to assign rootUser.children to the awaited result of Promise.all
I need to generate some objects, which represents DB documents and I need to 'connect' them via parent reference. The objects are pretty simple.
The main problem for me is to define the input
The objects should be connected in a variable way: I would like to call a function with some parameters to get the result data.To make it a bit easier to understand here an example.
The structure of the needed result data could look like this:
- main
- group
- group
- item
- item
- item
- main
So for this the result output should be:
[
{ _id: 'main1ID' },
{ _id: 'group1ID', parent: 'main1ID', type: 'group' },
{ _id: 'group2ID', parent: 'main1ID', type: 'group' },
{ _id: 'item1ID', parent: 'group1ID', type: 'item' },
{ _id: 'item2ID', parent: 'group1ID', type: 'item' },
{ _id: 'item3ID', parent: 'main1ID', type: 'item' },
{ _id: 'main2ID' },
]
As you can see a main element can have group or items as children or even have no child.
Groups can also have no child at all or items as children.
So I tried to start with a basic function, but I even don't know how to define the parameters to get this function working dynamically :-(
generateContent(2, 2, 3) could create two main object, two groups and three items, but there is no information, how they should be connected to each other. And this is the main problem for me...
function generateContent (main = 0, group = 0, item = 0) {
const mainDocs = []
for (var i = 0; i <= main; i++) {
mainDocs.push({
_id: Random.id()
})
}
// do similiar thing for group and item
// return everything
}
You would need to encode in your input the relations. It is not enough to indicate the number of each type of node. There are several encodings you could think of. A very concise one would be a string pattern like the following:
m(gg(ii)i)m
Here each letter represents the type of node to produce, and the parentheses make clear how they should relate to each other.
Here is a function that would parse such a string into the final array:
function generateContent (pattern) {
const mainDocs = [],
parents = [],
count = { m:0, g:0, i:0 };
let obj, parent;
for (let ch of pattern) {
if (ch === '(') {
parents.push(parent);
parent = obj._id;
} else if (ch === ')') {
parent = parents.pop();
} else {
let type = { m: 'main', g: 'group', i: 'item' }[ch];
obj = {
_id: type + (++count[ch]) + 'ID'
};
if (parent) obj.parent = parent;
if (ch !== 'm') obj.type = type;
mainDocs.push(obj);
}
}
return mainDocs;
}
// Example:
const result = generateContent('m(gg(ii)i)m');
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
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];
}
}
}
}