I have a flat array. Like this:
const inputArray = [
{
path: '1',
id: '1'
},
{
path: '2',
id: '2'
},
{
path: '3',
id: '3'
},
{
path: '3.4',
id: '4'
},
{
path: '3.5',
id: '5'
},
{
path: '3.4.6',
id: '6'
},
{
path: '3.4.7',
id: '7'
},
{
path: '8',
id: '8'
},
]
Where path is unique path of the element by id. For example, path: '3.5' means that this object is child for object with id: '3'. And path: '3.4.6' is child for path: '3.4'.
I want collect them to nested structure. So result should be like this.
const result = [
{
path: '1',
id: '1',
children: []
},
{
path: '2',
id: '2',
children: []
},
{
path: '3',
id: '3',
children: [
{
path: '3.4',
id: '4',
children: [
{
path: '3.4.6',
id: '6',
children: []
},
{
path: '3.4.7',
id: '7',
children: []
},
]
},
{
path: '3.5',
id: '5',
children: []
},
]
},
{
path: '8',
id: '8',
children: []
},
]
And also i need a second algorithm to convert them back, from nested to flat structure. Can you give ad advice please?
UPDATE: Data is not sorted. Here is my try, but there is too much code and it fails in some cases. I feel that there should be better way to make this.
Use Array.reduce, Array.findIndex, Array.push and Array.shift
Convert to Tree
Assuming that input array is sorted by path, otherwise, you will not sort inputArray.sort((a,b) => a.path - b.path);
Reduce the array to form the tree
Create an array of the hierarchy by splitting the path and creating a number array from it
Create a function addChildren which will take 3 inputs
a -> Parent object (array) in which object will be inserted
c -> object that needs to be inserted
t -> array of hierarchy of the object which needs to be inserted
Function takes the first value of t and if its last in hierarchy then
it means that the a is the valid placeholder for the object. Hence,
push it there. In case there are remaining values then find the
placeholder from the array by matching the id. Now, again call the
function with a will become the children array of the matched object,
c remains the same and t will be the remaining array of hierarchy.
const inputArray = [{path:'1',id:'1'},{path:'2',id:'2'},{path:'3',id:'3'},{path:'3.4',id:'4'},{path:'3.5',id:'5'},{path:'3.4.6',id:'6'},{path:'3.4.7',id:'7'},{path:'8',id:'8'}];
const result = inputArray.reduce((a,c) => {
let t = c.path.split(".").map(Number);
addChildren(a,c,t);
return a;
}, []);
function addChildren(a, c, t) {
let val = t.shift();
if(!t.length) {
a.push({...c, children : []});
} else {
var i = a.findIndex(({id}) => Number(id) === val);
addChildren(a[i].children, c, t);
}
}
console.log(result);
Flatten Tree
Create a function that take 2 inputs
a -> Input array (Array of children)
r -> Resulting array
Function iterates over the input array and pushes the objects in the
result array and check for any children, if yes, then call the function
for children as well
var inputArray = [{path:'1',id:'1',children:[]},{path:'2',id:'2',children:[]},{path:'3',id:'3',children:[{path:'3.4',id:'4',children:[{path:'3.4.6',id:'6',children:[]},{path:'3.4.7',id:'7',children:[]},]},{path:'3.5',id:'5',children:[]},]},{path:'8',id:'8',children:[]},];
function flattenArray(a, r) {
a.forEach(({children, ...rest}) => {
r.push(rest);
if(children) flattenArray(children, r)
});
}
var result = [];
flattenArray(inputArray, result);
console.log(result);
If you want more versability, you can create tree-structure from the input and then do whatever you want with it (i.e. output in some format you want, add methods for finding some item etc.)
const inputArray = [
{
path: '1',
id: '1'
},
{
path: '2',
id: '2'
},
{
path: '3',
id: '3'
},
{
path: '3.4',
id: '4'
},
{
path: '3.5',
id: '5'
},
{
path: '3.4.6',
id: '6'
},
{
path: '3.4.7',
id: '7'
},
{
path: '8',
id: '8'
},
];
class Tree {
constructor() {
this.root = {};
}
addNewNode(path, node) {
const pathArr = path.split('.');
let currentNode = this.root;
pathArr.forEach(item => {
if (!currentNode[item]) {
currentNode[item] = {};
}
currentNode = currentNode[item];
});
currentNode.data = node;
}
}
const tree = new Tree();
inputArray.forEach(val => tree.addNewNode(val.path, val));
console.log(tree);
The output is the output of a tree, not the exact output you included - it is up to you, how you proceed with it.
You could use an object as helper structure for unsorted data and build a tree.
For getting a flat array, you could iterate the tree and concat the flat children with a recursive function.
function getTree(array) {
var o = {};
array.forEach(({ id, path }) => {
var parents = path.split('.'),
parent = parents[parents.length - 2];
Object.assign(o[id] = o[id] || {}, { id, path });
o[parent] = o[parent] || {};
o[parent].children = o[parent].children || [];
o[parent].children.push(o[id]);
});
return o.undefined.children;
}
function getFlat(array = []) {
return array.reduce((r, { id, path, children }) =>
r.concat({ id, path }, getFlat(children)), []);
}
var input = [{ path: '1', id: '1' }, { path: '2', id: '2' }, { path: '3', id: '3' }, { path: '3.4', id: '4' }, { path: '3.5', id: '5' }, { path: '3.4.6', id: '6' }, { path: '3.4.7', id: '7' }, { path: '8', id: '8' }],
tree = getTree(input),
flat = getFlat(tree);
console.log(tree);
console.log(flat);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A solution without keeping path.
function getTree(array) {
var o = {};
array.forEach(({ id, path }) => {
var parents = path.split('.'),
parent = parents[parents.length - 2];
Object.assign(o[id] = o[id] || {}, { id });
o[parent] = o[parent] || {};
o[parent].children = o[parent].children || [];
o[parent].children.push(o[id]);
});
return o.undefined.children;
}
function getFlat(array = [], path = []) {
return array.reduce((r, { id, children }) => {
var p = path.concat(id);
return r.concat({ id, path: p.join('.') }, getFlat(children, p));
}, []);
}
var input = [{ path: '1', id: '1' }, { path: '2', id: '2' }, { path: '3', id: '3' }, { path: '3.4', id: '4' }, { path: '3.5', id: '5' }, { path: '3.4.6', id: '6' }, { path: '3.4.7', id: '7' }, { path: '8', id: '8' }],
tree = getTree(input),
flat = getFlat(tree);
console.log(tree);
console.log(flat);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Related
in an arry of objects i want to remove object which have same id (duplicated data) using javascript.
below is the input array
const input = [
{
id: '1',
name: 'first',
},
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
{
id: '2',
name: 'second',
},
]
so as you see from above array there are duplicating data with id '1' and '2'.
if there is similar id i want include only one
so the expected output is like below,
const output = [
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
]
how can i do this. could someone help me with this. i am new to programming thanks.
You can use reduce to filter data from the array based on some condition like bellow
const input = [
{
id: '1',
name: 'first',
},
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
{
id: '2',
name: 'second',
},
]
const result = input.reduce((accumulator, current) => {
let exists = accumulator.find(item => {
return item.id === current.id;
});
if(!exists) {
accumulator = accumulator.concat(current);
}
return accumulator;
}, []);
console.log(result);
Similar to this answer. You will have to change the const to let while declaring input though, or use a new variable I suppose.
filtered_input = input.filter((value, index, self) =>
index === self.findIndex((t) => (
t.id === value.id
))
)
There is a lot of good approachs here.
Here is my approach for removing matching property from the original array and sending it back in the return if found.
I prefer to use this one, if you are looping through a external array and matching them, this way you dont need to loop through the whole array again and again for each, because while you are finding the matches it keeps removing them from the original array, increasing performance.
Note that this will return the first match found
let id = "601985b485d9281d64056953"
let contacts = [{
...,
parent: "601985b485d9281d64056953",
...,
},
{
...,
parent: "601985b485d9281d64065128",
...,
}
]
function findAndRemoveObjectFromArray(array, internalProperty, externalProperty, convertType = "string", returnObject = false) {
let objIndex = -1
if (convertType === "string") objIndex = array.findIndex((obj) => String(obj[`${internalProperty}`]) === String(externalProperty));
if (convertType === "number") objIndex = array.findIndex((obj) => Number(obj[`${internalProperty}`]) === Number(externalProperty));
if (objIndex > -1) {
const object = array.splice(objIndex, 1);
if (returnObject) return object.shift()
return object
}
return [];
}
let currentContact = findAndRemoveObjectFromArray(contacts, "parent", id, 'string', true)
// Results:{..., parent: "601985b485d9281d64056953",...}
you could use Set to get rid of the duplicate data like this
const input = [
{
id: '1',
name: 'first',
},
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
{
id: '2',
name: 'second',
},
]
const result = [...new Set(input.map(JSON.stringify))].map(JSON.parse)
console.log(result)
Below is another approach
const input = [
{
id: '1',
name: 'first',
},
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
{
id: '2',
name: 'second',
},
];
const uniqueIds = new Set();
const uniqueList = input.filter(element => {
const isDuplicate = uniqueIds.has(element.id);
uniqueIds.add(element.id);
return !isDuplicate;
});
console.log(uniqueList);
I have an array of objects as the following
const sample = [
{ id: '1' },
{ id: '1.1' },
{ id: '1.1.1' },
{ id: '1.1.2' },
{ id: '1.2' },
{ id: '1.2.1' },
{ id: '1.2.1.1' },
{ id: '2' },
{ id: '2.1' }
];
I'd like to create a new array to include the children under their parent based on id property as the following
[
{
id: '1',
children: [
{
id: '1.1',
children: [
{ id: '1.1.1' },
{ id: '1.1.2' }
]
},
{
id: '1.2',
children: [
{
id: '1.2.1',
children: [{ id: '1.2.1.1' }]
}
]
}
]
},
{
id: '2',
children: [ { id: '2.1' } ]
}
]
I'm not sure how to do it or from where to start
Use a map to keep track of parents and children, then get the entries that are the roots as your result:
const data = [
{ id: '1' },
{ id: '1.1' },
{ id: '1.1.1' },
{ id: '1.1.2' },
{ id: '1.3' },
{ id: '1.3.1' },
{ id: '1.3.1.1' },
{ id: '2' },
{ id: '2.1' }
];
const map = new Map();
data.forEach(({ id }) => {
// exclude last bit to get parent id
const parent = id.split(".").slice(0, -1).join(".");
// our entry - needs to be like this since
// we want a reference to the same object
const entry = { id, children: [] };
// won't run if this is a root
if (parent)
// add child to parent
map.get(parent).children.push(entry);
// add to map
map.set(id, entry);
});
const result = Array.from(map)
// get roots - keys that only have one part
.filter(([key]) => key.split(".").length === 1)
// map to entry values
.map(([, value]) => value);
console.log(result);
.as-console-wrapper { max-height: 100% !important }
I have an array of objects like below:
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
}
]
I have to find out the count of type: 'MAIN'. these 'MAIN' will be within type: "type2"
So the expected count is 6. The outer children array can be empty and sometimes inner children array with type: "type2" is not there at all examples like below:
children: [] //empty array
children: [
{
id: '1',
children: [],
type: 'TYPE1',
},
] //no children with type: 'TYPE2'
Could someone help me with this? New to programming.
EDIT:
what i have tried?
const findAllChildrenOfType = (obj, type) => {
let count = 0;
if (obj.type === type) count++;
if (obj.children) {
obj.children.forEach(child => {
const childCount = findAllChildrenOfType(child,
"MAIN");
count += childCount;
})
}
return count;
}
findAllChildrenOfType(arr_obj, "TYPE2");
But this gives me count always 0.
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
],
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
],
type: 'TYPE2',
}
];
let count = 0
const findAllChildrenOfType = (obj, type) => {
if (obj?.type === type) count++;
if(obj?.children?.length) {
obj.children.forEach((child) => {
findAllChildrenOfType(child, type);
});
}
// return count;
};
arr_obj.forEach((child) => {
if(child.type === 'TYPE2') {
findAllChildrenOfType(child, "MAIN");
}
});
console.log(count);
You could create a small function inside your findAllChildrenOfType() function, that will iterate over the array, and increment the count if it encounters an object with given type. Then you can return the count from the parent function, by subtracting the number of objects with initial type ("TYPE2" in this case), since they also get added up, when you increment the count in if(obj.type === type).
const arr_obj=[{id:"1",children:[],type:"TYPE1"},{id:"2",children:[{id:"1",children:[{}],type:"MAIN"},{id:"2",children:[{}],type:"MAIN"},{id:"3",children:[{}],type:"MAIN"}],type:"TYPE2"},{id:"3",children:[{id:"4",children:[{}],type:"MAIN"},{id:"5",children:[{}],type:"MAIN"},{id:"6",children:[{}],type:"MAIN"}],type:"TYPE2"}];
const findAllChildrenOfType = (arr_obj, type) => {
let count = 0;
let findElem = (arr_obj, type) => {
arr_obj.forEach(obj => {
if (obj.type === type) {
count++;
if (obj.children.length) {
return findElem(obj.children, "MAIN");
}
}
});
return count;
}
count = findElem(arr_obj, type);
let fil = arr_obj.filter(obj => obj.type === type).length;
return count - fil;
}
console.log(findAllChildrenOfType(arr_obj, "TYPE2"));
A small recursive loop.
const data=[{id:"1",children:[],type:"TYPE1"},{id:"2",children:[{id:"1",children:[{}],type:"MAIN"},{id:"2",children:[{}],type:"MAIN"},{id:"3",children:[{}],type:"MAIN"}],type:"TYPE2"},{id:"3",children:[{id:"4",children:[{}],type:"MAIN"},{id:"5",children:[{}],type:"MAIN"},{id:"6",children:[{}],type:"MAIN"}],type:"TYPE2"}];
const data2=[{id:"1",children:[],type:"TYPE1"},{id:"2",children:[{id:"1",children:[{}],type:"MAIN"},{id:"2",children:[{}],type:"MAIN"},{id:"3",children:[{}],type:"MAIN"}],type:"TYPE2"},{id:"3",children:[{id:"4",children:[{}],type:"MAIN"},{id:"5",children:[{}],type:"MAIN"},{id:"6",children:[{}],type:"MAIN"},{id:"7",children:[{}],type:"MAIN"},{id:"8",children:[{}],type:"MAIN2"}],type:"TYPE2"}];
function find(arr) {
let count = 0;
function loop(arr) {
for (const obj of arr) {
const { type, children } = obj;
if (type === 'TYPE2') loop(children);
if (type === 'MAIN') ++count;
}
}
loop(arr);
return count;
}
console.log(find(data));
console.log(find(data2));
Instead of use it with all object i use forEach and use function for each object like:
const arr_obj = [{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [{
id: '1',
children: [{
//some attributes
}],
type: 'MAIN',
},
{
id: '2',
children: [{
//some attributes
}],
type: 'MAIN',
},
{
id: '3',
children: [{
//some attributes
}],
type: 'MAIN',
},
],
type: 'TYPE2',
},
{
id: '3',
children: [{
id: '4',
children: [{
//some attributes
}],
type: 'MAIN',
},
{
id: '5',
children: [{
//some attributes
}],
type: 'MAIN',
},
{
id: '6',
children: [{
//some attributes
}],
type: 'MAIN',
},
],
type: 'TYPE2'
}
];
const findAllChildrenOfType = (obj, type) => {
let count = 0;
if (obj.type === type) {
if (type === 'MAIN') count++;
if (obj.children) {
obj.children.forEach(child => {
const childCount = findAllChildrenOfType(child,
"MAIN");
count += childCount;
})
}
}
return count;
}
let countAll = 0;
arr_obj.forEach(el => {
countAll += findAllChildrenOfType(el, "TYPE2");
});
console.log(countAll)
Say I have the following object:
const pages = [
{
name: 'a',
id: '1',
pages: [
{
name: 'b',
id: '1.1',
pages: []
},
{
name: 'c',
id: '1.2',
pages: [
{
name: 'd',
id: '1.2.1',
pages: []
}
]
},
]
},
{
name: 'e',
id: '2',
pages: []
}
]
I'd like to perform a function on this nested object that will return the 'path' to the object I'm searching for.
So something like
getPath(pages, '1.2.1')
will return:
[
{
name: 'a',
id: '1'
},
{
name: 'c',
id: '1.2'
},
{
name: 'd'
id: '1.2.1'
}
]
Here's what I have so far. Its just a recursive function to find the object I want. I'm stuck on how to build the path as I'm traversing through the object though.
const pages = [
{
name: 'a',
id: '1',
pages: [
{
name: 'b',
id: '1.1',
pages: []
},
{
name: 'c',
id: '1.2',
pages: [
{
name: 'd',
id: '1.2.1',
pages: []
}
]
},
]
},
{
name: 'e',
id: '2',
pages: []
}
]
function getPath(pages, pageId) {
let path = [];
for (let i = 0; i < pages.length; i++) {
const item = search(pages[i], pageId);
// build path here?
if (item) {
return item;
}
}
}
function search(obj, id) {
if (obj.id === id) {
return obj;
}
for (let i = 0; i < obj.pages.length; i++) {
const possibleResult = search(obj.pages[i], id);
if (possibleResult) {
return possibleResult;
}
}
}
console.log(getPath(pages, '1.2.1'))
You can use this alternative for getting the path, it's a recursive approach and uses an array called path as param in order to track the visited levels.
Assuming the ID's are uniques regardless of the location/level.
const pages = [ { name: 'a', id: '1', pages: [ { name: 'b', id: '1.1', pages: [] }, { name: 'c', id: '1.2', pages: [ { name: 'd', id: '1.2.1', pages: [] } ] }, ] }, { name: 'e', id: '2', pages: [] }];
const loop = (arr, target, index, path) => {
if (arr[index].id === target) {
path.push({name: arr[index].name, id: arr[index].id});
} else if (arr[index].pages.length) {
path.push({name: arr[index].name, id: arr[index].id});
arr[index].pages.forEach((_, i, a) => {
loop(a, target, i, path);
});
if (path[path.length - 1].id === arr[index].id) path.pop();
}
};
let getPath = (arr, target) => {
let path = [];
arr.forEach((_, i, a) => loop(a, target, i, path));
return path;
};
console.log(getPath(pages, '1.2.1'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I have a big problem. I want to create a function that remove equal 'parts' between two JSON files, with the output of function having the same structure, but without the 'equal parts'.
An example i have a JSON version of a DOM tree and i want keep only the difference between pages (remove nav footer ...)
Example
const a = {
id: '1',
child: [
{
id: '2',
child: [
{
id: '1'
},
{
id: '2'
}
]
},
{
id: '3',
child: [
{
id: '1'
},
{
id: '5'
}
]
}
]
}
And
const b = {
id: '1',
child: [
{
id: '2',
child: [
{
id: '1'
},
{
id: '4'
}
]
},
{
id: '3',
child: [
{
id: '1'
},
{
id: '4'
}
]
}
]
}
With a function
diff(a, b)
This result
{
id: '1',
child: [
{
id: '2',
child: [
{
id: '2'
}
]
},
{
id: '3',
child: [
{
id: '5'
}
]
}
]
}
I created this based on recursive function
const diff = (a, b) => {
if (Array.isArray(a)) {
}
if (typeof a === 'object') {
// ...
extract(a.child, b.child);
}
}
How do I do this? Is there an npm package? or with JSON Path? I want to create a function that remove the equal 'parts' between two JSON files with the output of the function having the same structure, but without the 'equal parts'.
I assume you can't guarantee the order of the id/value pairs?
I would suggest that you first recursively merge each layer, then go through and remove duplicates.
Edited With Code from the Recursion function
let c = [] // merged structure
for (var i=0; i<a.length; i++)
{
for (let j=0; j<b.length; j++)
{
if (a[i].id === j[i].id) {
c[i] = { id: a[i].id, child: a[i].child.concat(b[i].child) }
// next, sort the c[i].child, and
// loop through c[i].child and recur over any cases where
// c[i].child[k].id === c[i].child[k+1].id
// at the end, go back up the chain removing any structs where the
// id is a duplicate and has no children left
}
}
}