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; }
Related
I am executing the map method on an array selectedItem where selectedItem has a string property nodeType and an object property items :
items = { computers: 0, drives: 0, files: 0, folders: 0 }
selectedItem?.map(item => {
switch (item.nodeType) {
case 'FileType':
return (items.files += 1);
case 'FolderType':
return (items.folders += 1);
case 'DriveType':
return (items.drives += 1);
case 'ComputerType':
return (items.computers += 1);
default:
return;
}
I know I should be able to replace the switch statement by using the built-in functionality of the map method (hasOwnProperty), but I've only seen examples where Object literals are used to match on in order to return the string, like this:
const itemTypes = {
file: 'file',
folder: 'folder',
drive: 'drive',
computer: 'computer'
}
However, upon a .map(expression) match I want to execute a simple incrementing logic to +=1 to a property within the object I'm evaluating.
I've looked at .filter() and .reduce() but of course don't apply since they return a different array. I'm sure it may have to deal with writing the correct function within the .map(expression) but not sure how to do that.
I've looked at these SO posts, but they either don't apply or don't get me all the way there. The second link is very close to what I'm looking for but not sure how to apply it to my code:
Update multiple objects using Javascript map() function
Update Map object property values
How would I do that using only map and not nesting a switch within the map function?
map() is used to create a new array. It is not useful when you want to update an object.
You should be using reduce to count up all the types
const selectedItem = [
{ nodeType: 'FolderType' },
{ nodeType: 'DriveType' },
{ nodeType: 'DriveType' },
{ nodeType: 'FolderType' },
{ nodeType: 'ComputerType' },
];
const types = {
FileType: 'files',
FolderType: 'folders',
DriveType: 'drives',
ComputerType: 'computers'
};
const items = {
computers: 0,
drives: 0,
files: 0,
folders: 0
};
selectedItem?.reduce((obj, item) => {
const type = types[item.nodeType];
if (type) obj[type]++;
return obj;
}, items);
console.log(items);
or forEach
const selectedItem = [
{ nodeType: 'FolderType' },
{ nodeType: 'DriveType' },
{ nodeType: 'DriveType' },
{ nodeType: 'FolderType' },
{ nodeType: 'ComputerType' },
];
const types = {
FileType: 'files',
FolderType: 'folders',
DriveType: 'drives',
ComputerType: 'computers'
};
const items = {
computers: 0,
drives: 0,
files: 0,
folders: 0
};
selectedItem?.forEach((item) => {
const type = types[item.nodeType];
if (type) items[type]++;
});
console.log(items);
You can use a map object to associate the node types to their items keys.
const itemsKeyByNodeType = {
FileType: 'files',
FolderType: 'folders',
DriveType: 'drives',
ComputerType: 'computers'
};
selectedItem?.forEach(item => {
if (itemsKeyByNodeType[item.nodeType]) {
items[itemsKeyByNodeType[item.nodeType]]++;
}
});
In plain javascript, I am trying to create a function that will return a tree structure (json) of a folder, its subfolders and any files. I'm trying to achieve this using recursion.
The problem with the below code is that it stops after the first recursive call.
I know that in JS you do references, and I need to create a new object that I pass the values from the previous call to, but I am struggling to do so.
function fun(file, json) {
var tempJson = {
'name' : json.name || '',
'children' : obj.children || new Object()
};
if (file.type == 'file') {
tempJson.type = 'file';
tempJson.children = {}; // this will be empty, since there are no children
}
else {
tempJson.type = 'dir';
var listed = file.listFiles();
if (listed.length > 0) {
for each (var item in listed) {
tempJson.children = fun(item, tempJson);
}
} else {
tempJson.children = {};
}
}
return tempJson;
}
Example
From a directory structure like:
-root
--file1
--dir1
---file1.1
--dir2
I would like to get a json like:
{
name: 'root',
type: 'dir',
children : [
{
name: 'file1',
type: 'file',
children: {}
},
{
name: 'dir1',
type: 'dir',
children:
{
name: 'file1.1',
type: 'file',
children: {},
}
},
name: 'dir2',
type: 'dir',
children: {}
}
First call:
var object = new Object();
fun(rootdir, object);
Hope this makes sense.
Thanks!
As pointed out in the comments, children should be an array:
function fun(entry) {
var entryObj = { // construct the object for this entry
name: entry.name || "",
type: entry.type, // put the type here instead of using an if
children: [] // children must be an array
};
if(entry.type === "dir") { // if this entry is a directory
var childEntries = entry.listFiles(); // get its child entries
for(var childEntry of childEntries) { // and for each one of them
entryObj.children.push(fun(childEntry)); // add the result of the call of 'fun' on them to the children array
}
}
return entryObj;
}
Then call it like so:
var tree = fun(rootEntry);
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]);
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);
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];
}
}
}
}