I'm trying to combine and group an array with a bunch of flat arrays that contain only strings, no objects.
So my array looks something like this:
var array = [
["MotherNode", "Node1", "ChildNode1", "ChildOfChildNode1"],
["MotherNode", "Node1", "ChildNode2", "ChildOfChildNode2"],
["MotherNode", "Node2", "ChildNode3", "ChildOfChildNode3"],
["MotherNode", "Node2", "ChildNode3", "ChildOfChildNode4"],
["MotherNode", "Node3", "ChildNode4", "ChildOfChildNode5"],
["MotherNode", "Node3", "ChildNode4", "ChildOfChildNode5"]
]
Im doing this in javascript/angularjs and so far I've gathered that the best solution is probably to use underscore.js groupBy/combine methods. However most of the examples that i can find are dealing with arrays of objects where they can group them together by using a value's key. And I'm not good enough with algorithms yet to be able to figure this out on my own.
The array I'm dealing with can have hundreds of values and the result array could get 5-10 levels deep.
The result I'd like by parsing the above array would look something like this:
var result= {
"MotherNode": [{
"Node1":[{
"ChildNode1":"ChildOfChildNode1"
},{
"ChildNode2":"ChildOfChildNode2"
},{
"Node2":[{
"ChildNode3":["ChildOfChildNode3","ChildOfChildNode4"]
},{
"Node3":[{
"ChildNode4":"ChildOfChildNode5"
}
]
}
So does anyone have any clue how this can be done? I'm completely out of ideas.
I solved this using _.reduce grouping wasnt the way to go
var result = _.reduce(array,function(memo, val){
var tmp = memo;
_.each(val, function(fldr){
if(!_.has(tmp, fldr)){
tmp[fldr] = {}
}
tmp = tmp[fldr]
})
return memo
},{})
the end leaf wont be set as a value but it should be easy to change that behavior to whatever suits you use case
{ MotherNode:
{ Node1:
{ ChildNode1: { ChildOfChildNode1: {} },
ChildNode2: { ChildOfChildNode2: {} } },
Node2: { ChildNode3: { ChildOfChildNode3: {}, ChildOfChildNode4: {} } },
Node3: { ChildNode4: { ChildOfChildNode5: {} } } } }
Related
I have the following JSON Structure:
{
"list":[
{
"id":"7ccba2c3-e6df-4cb2-94b9-2f6320366ce0",
"name":"List 1",
"people":[
{"id":"b6cb6317-8f89-4826-b60b-aa0ecf51b1af","name":"Person 1","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]},
{"id":"f2620c63-37cc-45be-86e0-fff5d415e483","name":"Person 2","mediaAccounts":[{"socialNetwork":"twitter"},{"socialNetwork":"facebook"}]},
{"id":"e8ccae28-6695-435b-ba61-dbf569e75665","name":"Person 3","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]}
]
},
{
"id":"a2d1d55a-e6df-4cb2-94b9-000000011212",
"name":"List 2",
"people":[
{"id":"b6cb6317-8f89-4826-b60b-aa0ecf51b1af","name":"Person 4","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]},
{"id":"f2620c63-37cc-45be-86e0-fff5d415e483","name":"Person 5","mediaAccounts":[{"socialNetwork":"twitter"},{"socialNetwork":"facebook"}]},
{"id":"e8ccae28-6695-435b-ba61-dbf569e75665","name":"Person 6","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]}
]
}
]
}
and I need to group people in a only one array like this:
{
"people": [
{"id":"b6cb6317-8f89-4826-b60b-aa0ecf51b1af","name":"Person 1","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]},
{"id":"f2620c63-37cc-45be-86e0-fff5d415e483","name":"Person 2","mediaAccounts":[{"socialNetwork":"twitter"},{"socialNetwork":"facebook"}]},
{"id":"e8ccae28-6695-435b-ba61-dbf569e75665","name":"Person 3","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]},
{"id":"b6cb6317-8f89-4826-b60b-aa0ecf51b1af","name":"Person 4","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]},
{"id":"f2620c63-37cc-45be-86e0-fff5d415e483","name":"Person 5","mediaAccounts":[{"socialNetwork":"twitter"},{"socialNetwork":"facebook"}]},
{"id":"e8ccae28-6695-435b-ba61-dbf569e75665","name":"Person 6","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]}
]
}
using underscore groupBy function, the return remains the same.
Anyone has any idea how can I group this content above?
Thanks in advance.
You can use Array.flatMap() (or lodash's _.flatMap()) to flatten the list of people to as single array:
const data = {"list":[{"id":"7ccba2c3-e6df-4cb2-94b9-2f6320366ce0","name":"List 1","people":[{"id":"b6cb6317-8f89-4826-b60b-aa0ecf51b1af","name":"Person 1","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]},{"id":"f2620c63-37cc-45be-86e0-fff5d415e483","name":"Person 2","mediaAccounts":[{"socialNetwork":"twitter"},{"socialNetwork":"facebook"}]},{"id":"e8ccae28-6695-435b-ba61-dbf569e75665","name":"Person 3","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]}]},{"id":"a2d1d55a-e6df-4cb2-94b9-000000011212","name":"List 2","people":[{"id":"b6cb6317-8f89-4826-b60b-aa0ecf51b1af","name":"Person 4","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]},{"id":"f2620c63-37cc-45be-86e0-fff5d415e483","name":"Person 5","mediaAccounts":[{"socialNetwork":"twitter"},{"socialNetwork":"facebook"}]},{"id":"e8ccae28-6695-435b-ba61-dbf569e75665","name":"Person 6","mediaAccounts":[{"socialNetwork":"facebook"},{"socialNetwork":"twitter"}]}]}]}
const result = {
people: data.list.flatMap(o => o.people)
}
console.log(result)
I have a nested object and a path, which describes a position in the object. I would like to access the object at the end of the tree with the given path. For example I would like to get the id of the object, where the path is "contracts/access/roles/MinterRole.sol". How could I do it? Is it possible without recursively iterating over the whole tree? Thank you!
My object looks like the following:
{
"name":"contracts",
"toggled":true,
"id":0,
"children":[
{
"name":"access",
"toggled":false,
"id":1,
"children":[
{
"name":"Roles.sol",
"id":2,
"path":"contracts/access/Roles.sol",
"dependencies":[
]
},
{
"name":"roles",
"toggled":false,
"id":3,
"children":[
{
"name":"CapperRole.sol",
"id":4,
"path":"contracts/access/roles/CapperRole.sol",
"dependencies":[
{
"fileName":"Roles.sol",
"absolutePath":"contracts/access/Roles.sol"
}
]
},
{
"name":"MinterRole.sol",
"id":5,
"path":"contracts/access/roles/MinterRole.sol",
"dependencies":[
{
"fileName":"Roles.sol",
"absolutePath":"contracts/access/Roles.sol"
}
]
},
{
"name":"PauserRole.sol",
"id":6,
"path":"contracts/access/roles/PauserRole.sol",
"dependencies":[
{
"fileName":"Roles.sol",
"absolutePath":"contracts/access/Roles.sol"
}
]
},
{
"name":"SignerRole.sol",
"id":7,
"path":"contracts/access/roles/SignerRole.sol",
"dependencies":[
{
"fileName":"Roles.sol",
"absolutePath":"contracts/access/Roles.sol"
}
]
}
]
}
]
}
]
}
I'd split the path at the slash, then just iterate through it. It makes it easier if you wrap the entire object in an array as well.
const pathParts = path.split("/");
let currentNode = mainTree;
pathParts.forEach(part => {
if (currentNode && currentNode.children) {
currentNode = currentNode.children.find(child => child.name === part);
}
});
Note that while this is iterative, it will still stop at the node on a given level that matches the path name, skipping anything after. As long as the tree isn't ordered (i.e. a binary tree or something), that's the best you can do.
Recursion is the way to go.
You may want to take a look at the semi-famous walk the DOM snippet by D. Crockford himself.
What you want to do is to adapt that snippet to your data and augment the code with extra logic to stop the recursion if a result has been found.
Take for example this extremely simplified Array of Objects:
[
{
createID: '1'
// Many other properties...
},
{
createID: '1'
// Many other properties...
},
{
createID: '1'
// Many other properties...
},
{
createID: '37'
// Many other properties...
},
{
createID: '37'
// Many other properties...
},
{
createID: '2'
// Many other properties...
},
{
createID: '2'
// Many other properties...
},
{
createID: '14'
// Many other properties...
},
];
Given this Array I then use the objects createID property to create an Array of Arrays containing Objects [[{..},{..}], [{..}], ..n]. This final format is required by the current front end framework I am using (Angular v6).
To accomplish this task I use the following code, where tempArr is an array like the example array provided above.
let currentGroup: string = tempArr[0].createID;
let tempGrouped: any[] = [];
let childGroup: any[] = [];
tempArr.forEach(item => {
if (item.createID !== currentGroup) {
tempGrouped.push(childGroup);
childGroup = [];
currentGroup = item.createID;
}
childGroup.push(item);
});
tempGrouped.push(childGroup);
This code works fine. However, I can't help but believe there must be a more efficient and elegant way given the data to convert an Array of objects into an Array of Arrays containing objects.
UpdateIt is important to note that the createID's are only id's that signify which objects should be grouped together. Therefore, they do not need to be numerically ordered by createID. In addition, the objects do come from the server "grouped" with their sibling objects (same createID) as you can see in the given example array provided.
Your example has all identical IDs adjacent to each other. If that is guaranteed to always be the case, looping though and pushing to a new array is all you need. However if this isn't the case, your solution will fail to group items properly. In that case using a hash table will allow you to still group by ID with same asymptotic complexity.
You can group your objects into a hash table object with keys created from createdID. This will let you group everything efficiently. Then just take the objects from the hash table:
let arr = [{createID: '1'},{createID: '1'},{createID: '1'},{createID: '37'},{createID: '37'},{createID: '2'},{createID: '2'},{createID: '14'},];
let o = arr.reduce((a, c) => {
(a[c.createID] || (a[c.createID] = [])).push(c)
return a
}, {} )
// o is a an object with createID keys pointing to arrays of grouped objects
// just take the values
console.log(Object.values(o))
Edit based on question edit
Since the objects will already be grouped, there's not a better way than looping through. If you want an option that doesn't add the temp arrays, you can still use reduce(), which is essentially the same as your current solution, but maybe a little more self contained:
let tempArr = [{createID: '1'},{createID: '1'},{createID: '1'},{createID: '37'},{createID: '37'},{createID: '2'},{createID: '2'},{createID: '14'},];
let r = tempArr.reduce((a, c, i, self) => {
if (i === 0 || self[i-1].createID !== c.createID)
a.push([])
a[a.length - 1].push(c)
return a
}, [])
console.log(r)
Assuming that your array of data is stored into a variable called data:
const result = data.reduce((acc, current) => {
if (!acc.dictionary[current.createID]) {
const createIdArray = [];
acc.dictionary[current.createID] = createIdArray;
acc.array.push(createIdArray);
}
acc.dictionary[current.createID].push(current);
return acc;
}, {array: [], dictionary: {}}).array;
This way, you'll loop only once on data, and it's efficient as we don't use filter or find (which would go through the whole array again and again).
Here's the output:
[
[
{
createID: '1',
},
{
createID: '1',
},
{
createID: '1',
},
],
[
{
createID: '37',
},
{
createID: '37',
},
],
[
{
createID: '2',
},
{
createID: '2',
},
],
[
{
createID: '14',
},
],
];
Here's a running demo: https://stackblitz.com/edit/typescript-phbzug
Summary:
The dictionary is self contained within the reduce function which means that as soon as the reduce is done, it'll be garbage collected
Not relying on any external variables, easier to reason about and IMO a better practice
This solution is more robust (the array doesn't need to be sorted) for ~ the same number of lines as OP's answer
Clean: With the dictionary you know directly what you're accessing and it's really fast
you want to group by createID?
let grouped=tempArray
//first get uniq values
.filter((s,index)=>tempArray.findIndex(f=>f.createID==s.createID)==index)
//then, with the uniq values make a map
.map(seg=>{ //with each uniq value, create an object with two properties
return {
createID:seg.createID, //the key
items:tempArray.filter(s=>s.createID==seg.createID) //An array with the values
}
})
I'm new to software Field. i have a json array of objects like
var treeObj = [
{
"name": "sriram",
"refernce_id": "SAN001",
"sponer_id": "SAN000"
},
{
"name": "neeraja",
"refernce_id": "SAN002",
"sponer_id": "SAN001"
},
{
"name": "upender",
"refernce_id": "SAN003",
"sponer_id": "SAN001"
},
{
"name": "manoj",
"refernce_id": "SAN004",
"sponer_id": "SAN002"
},
{
"name": "shirisha",
"refernce_id": "SAN005",
"sponer_id": "SAN002"
},
{
"name": "ragu",
"refernce_id": "SAN006",
"sponer_id": "SAN003"
},
{
"name": "santhu",
"refernce_id": "SAN007",
"sponer_id": "SAN003"
}
];
Here i will pass the above object to a function. in that function i need to compare reference id with sponer_id in every object and if they are equal we need to push them into an Array which we call as child object just like below and again we need to check in the child Array that the reference id in is present in sponer_id of above object if it is present again we need to push them to child array into object which contains the reference_id. the final array Object looks like.
[
{
"name": "sriram",
"parent": null,
"children": [
{
"name": "neeraja",
"parent": "sriram",
"children": [
{
"name": "manoj",
"parent": "neeraja"
},
{
"name": "shirisha",
"parent": "neeraja"
}
]
},
{
"name": "upender",
"parent": "sriram",
"children": [
{
"name": "ragu",
"parent": "neeraja"
},
{
"name": "santhu",
"parent": "neeraja"
}
]
}
]
}
]
Here sriram's reference id of treeObj is present as sponer id in neeraja and upender object. so neeraja and upender becomes child to sriram. and neeraja's reference_id is present as sponer_id in manoj and shirisha objects of treeObj. simultaneously the child can many more child objects and we need to format the object dynamically.
The function which i wrote looks like
var mainArr = [], subArrs = [], subObj={}, subIds = [], find = "SAN001";
formatData(treeObj);
function formatData(treeObj){debugger;
var arr = [];
for(var x=0; x<= treeObj.length-1; x++){debugger;
var sampData = treeObj[x];
if(find == sampData.sponer_id){
arr.push(sampData.refernce_id);
subArrs.push(sampData);
}
}
subIds.push(arr);
console.log(subIds);
console.log(subArrs);
formatData(subArrs);
}
please guide me where i went wrong. thanks in advance.
//1. find all items the have no parent and push them on a stack like so:
let stack = treeObj.reduce((list, item) => {
if (<ids match>) list.push(item);
return item;
}, []),
let result = [].concat(stack);
//2. while loop the stack:
while (stack.length > 0) {
let item = stack.shift();
item.children = treeObj.reduce((list, child) => {
if (<ids match>) {
list.push(child);
}
return list;
}, []).map(child => {
child.parent = item;
stack.unshift(item);
return child;
});
}
return result;
UPDATE
So in »good old JS« and with some improvements:
var stack = treeObj.filter(function (item) {
return item.<parent_id> === item.<child_id> });
var result = [].concat(stack);
while (stack.length > 0) {
var item = stack.shift();
item.children = treeObj.filter(function (child) {
return item.<id> === child.<parent_id>;
});
item.children.forEach(function (child) { stack.unshift(child) });
}
Basically:
find the root(s) and save them on the stack
while.length > 0
shift() the first item from the stack
find all children of that item and unshift them on the stack
Done
Adding a parent property to the items, or removing unneeded one, can be done in the loop. The whole thing can also be done using recursion, but I once ran in the »too much recursion error« by doing such stuff, so I prefer an iterative approach. And of course, instead of .reduce, .filter and .forEach, you can use regular loops, but I prefer a functional style. All in all, no matter how you do it, it is not that difficult, just find the elements to start with and then repeat with all of their children and so on. Withing the while loop all children are found, or, the whole subtree with that element as root.
Good luck!
You're basically trying to convert an array to a n-ary tree.
Entering your original tree function into formatData gives you an array (arr) with the referenceIDs of all objects having SAN001 as parent and another array (subArrs) with all children of with sponerId SAN001.
You then store the arr in subIds, log subIds and subArr and proceed to call format data on subArrs recursively. Then you check on subArr which objects have "SAN001 as predecessor" (which should be all objects at that time) and push that object in subArr. If im not getting it wrong this leads to an infinite loop.
Starting points for improvements:
you're "find" variable does not change, it's hardwired to "SAN001" - this would maybe be okay for your first trip when you're 100% sure that the root object has always this referenceID. But in the second trip, you want to check which objects depend on a second level element, so you need to set find to an corresponding referenceId.
Your subArrs contains all objects depending on SAN001. But in the second and following trips, you don't want to get childrens of SAN001 but child of objects in the subArr. So you need to travers old objects finding children of objects in subArr instead of traversing subArr seeking children of SAN001.
Hope that clear's it up a little bit.
A hint for further research: You're basically trying to "convert an array into a n-ary tree" with javascript.
Building on this angular recursive extend topic.
I have slightly modified the version...
var extendDeep = function(dst) {
angular.forEach(arguments, function(obj) {
if (obj !== dst) {
angular.forEach(obj, function(value, key) {
if (dst[key] && angular.isObject(dst[key])) {
extendDeep(dst[key], value);
} else if(!angular.isFunction(dst[key])) {
dst[key] = value;
}
});
}
});
return dst;
};
but found that it does not account for arrays correctly. For example, if I have a object like:
var obj = { zoo: [ 'moose', 'panda' ] };
and then I call like:
deepExtend(obj, {
zoo: [ 'panda' ]
})
at first glance you would expect it to remove the object, however, it actually ends up like:
{ zoo: [ 'panda', 'panda' ] }
now. The expected output would look like:
{ zoo: [ 'panda' ] }
This is a very simple case of course.
I'm not really looking for a 'unique' or a 'merge' like most solutions talk about. I need to add items that are missing from the left, remove items that are on the left but not on the right, and then iterate recursively and extend those objects in place.
There are many different ways to go about this, looking for feedback on a good approach.