Convert a string tree to array - javascript

I've got objects with an id as a string. Each object can be the child of another object. Relations can be guessed from IDs. For exemple:
[
{ id: '1:2:6', ids: ['1', '2', '6'] },
{ id: '1:4', ids: ['1', '4'] },
{ id: '1', ids: ['1'] },
{ id: '1:2', ids: ['1', '2'] },
]
In this exemple, root object is id: 1, which has 2 childrens id: 1:2 and id: 1:4. Finaly, id: 1:2 has a children id: 1:2:6.
I would like to convert this array to another array where childrens are embeded into parents, so the previous array would result in:
[
{
id: '1',
children: [
{
id: '1:2',
children: [
{ id: '1:2:6', children: [] }
],
},
{
id: '1:4',
children: [],
}
],
}
]
I can use ES6. I tried for hours to find a solution using all sort of loops but I can't figure this out. Any help would be appreciated!

You could iterate the objects and reduce ids by looking for an object at the actual level. If not found create a new object. Then return the children.
var data = [{ id: '1:2:6', ids: ['1', '2', '6'] }, { id: '1:4', ids: ['1', '4'] }, { id: '1', ids: ['1'] }, { id: '1:2', ids: ['1', '2'] }],
tree = data.reduce((r, { ids }) => {
ids.reduce((t, _, i, a) => {
var id = a.slice(0, i + 1).join(':'),
temp = t.find(o => o.id === id);
if (!temp) t.push(temp = { id, children: [] });
return temp.children;
}, r);
return r;
}, []);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

I think the iterative approach is more readable so maybe provided solution will help you understand how it is done (though it is lightly inspired by Nina's answer)
So what we do is iterate over all objects in the list.
Initially, for each object, we set the current nodeList as the final tree. And we iterate over the length of ids.
First, we create the id form the list of ids by dividing the ids array into incrementally larger chunks with slice (['1'], ['1','2'], ['1', '2', '6') and concatenate to a string with :. So we get 1, 1:2, 1:2:6 ids for the first item.
Next, we find a node in currentNodelist by previously constructed id. If we cannot find the node that means we have not added it yet so we need to create and add it (If we find it then we don't need to do add it).
In the next step, we need to go deeper into the tree so we assign the currently created(or the one that we found) node's children as currentNodelist. With this, we traverse the tree deeper by provided ids.
let objs = [
{ id: '1:2:6', ids: ['1', '2', '6'] },
{ id: '1:4', ids: ['1', '4'] },
{ id: '1', ids: ['1'] },
{ id: '1:2', ids: ['1', '2'] },
];
let tree = [];
for (let i = 0; i < objs.length; i++) {
let obj = objs[i];
let currentNodeList = tree;
for (let j = 0; j < obj.ids.length; j++) {
let id = obj.ids.slice(0, j + 1).join(':');
let currentNode = currentNodeList.find((node) => node.id === id);
if (!currentNode) {
currentNode = {id, children: []};
currentNodeList.push(currentNode);
}
currentNodeList = currentNode.children;
}
}
console.log(tree);
I created a simple gif that shows what is happening in the first 2 iterations. The arrow is pointing to currentListNode.

Figure out your algorithm "on paper" first. Let's start with an empty tree and take the first entry:
[1, 2, 6].
Node 1: add 1 to the tree - it's now the root and the last visited node.
Node 2: add child 2 to node 1.
Node 6: add child 6 to node 2.
When processing the next entry, [1, 4], 1 is already in the tree - just add 4 to it.
When processing the last entry, [1, 2], be mindful that 2 is also already in the tree.
P.S. It's "children" not "childs".

Building a tree using recursive algorithms
var jsonTree = [{ id: '1:2:6', ids: ['1', '2', '6'] },{ id: '1:4', ids: ['1', '4'] },{ id: '1', ids: ['1'] },{ id: '1:2', ids: ['1', '2'] },]
var newJsonTree = [];
var currentElement = {id: '1',childs: []}
newJsonTree.push(currentElement)
function buildTree(jsonTree, currentElement){
for(var i=0;i<jsonTree.length;i++){
var parent = jsonTree[i];
for(var j=0;j<jsonTree.length;j++){
var child = jsonTree[j];
if(child['visited'] != true && child['id'] != currentElement['id'] && child['id'].indexOf(currentElement['id']) == 0 ){
if(child['id'].split(":").length == currentElement['id'].split(":").length+1){
var newElement = {}
newElement['id'] = child['id'];
newElement['childs'] = [];
currentElement['childs'].push(newElement);
child['visited'] = true;
buildTree(jsonTree, newElement);
}
}
}
}
}
buildTree(jsonTree, currentElement);
document.write(JSON.stringify(newJsonTree));
result:
[{"id":"1","childs":[{"id":"1:4","childs":[]},{"id":"1:2","childs":[{"id":"1:2:6","childs":[]}]}]}]

Related

Find duplicates and update their values in JavaScript array

I was originally iterating through my array to remove any duplicates and only to return the unique values. However, I was wondering if it was possible to identify the duplicates and update their label in the original array in order to keep some of the original data that they contain. As of now, I been removing duplicates by doing the following:
const removeDuplicates = arrayCollection
.filter((link, i) => i === arrayCollection.findIndex((l) => (
l.collectionTitle === link.collectionTitle
)));
What I would like to do is for the collection title to attach something like "Link (0), Link (1)" if they share the same title. Overall, I'm trying to find a way to not remove the duplicates after finding them but rather to append something to differentiate them in the original array collection.
a sample of arrayCollection:
[
{id: '1', collectionTitle: '456'},
{id: '2', collectionTitle: '123'},
{id: '3', collectionTitle: '123'},
];
the desired output of updating duplicates:
[
{id: '1', collectionTitle: '456'},
{id: '2', collectionTitle: '123 (0)'},
{id: '3', collectionTitle: '123 (1)'},
];
First I group by the title so I can count how many of each. I use the famous array reduce method for that. Then use that grouped object to reconstruct the names.
var arrayCollection = [
{id: 1, collectionTitle: "Hello"},
{id: 2, collectionTitle: "Goodbye"},
{id: 3, collectionTitle: "Hello"},
];
var obj = arrayCollection.reduce(function(agg, item) {
agg[item.collectionTitle] = agg[item.collectionTitle] || []
agg[item.collectionTitle].push(item)
return agg;
}, {})
var result = [];
Object.values(obj).forEach(function(arr) {
if (arr.length == 1) {
result.push(arr[0])
} else {
for (var i = 0; i < arr.length; i++) {
arr[i].collectionTitle += " (" + i + ")";
result.push(arr[i])
}
}
})
console.log(result)
One of the fastest and most customizable ways to do this:
let result = [];
let dup = {}
for (let x = 0; x < arr.length; x++){
if (dup[arr[x]]) {
result.push(‘whatever u want’);
} else {
result.push(arr[x]);
dup[arr[x]] = true;
}
}
If you don't mind that first duplicate is not labelled, this will work:
const input = [
{id: '1', collectionTitle: '456'},
{id: '2', collectionTitle: '123'},
{id: '3', collectionTitle: '123'},
]
const result = input.map((current, i, array) => {
// Number of duplicates so far
const duplicatesCount = array
.slice(0, i)
.filter(el => el.collectionTitle.startsWith(current.collectionTitle))
.length
return {
id: current.id,
collectionTitle: duplicatesCount > 0
? `${current.collectionTitle} (${duplicatesCount})`
: current.collectionTitle,
}
})
console.log(result)
It is not a good solution performance-wise (nested .slice and .filter inside .map), but the code itself is relatively concise.

atleast one of the value's in the array1 obj "data" array matches the "data" array in array2 object

at least one of the value's in the array1 obj "data" array matches the "data" array in array2 object
> array1
array1 = [
{
id: '1',
name: 'ron',
data: ['p1']
},
{
id: '2',
name: 'lon',
data: ['p2']
},
{
id: '3',
name: 'voon',
data: ['p4']
}
];
> array2
array2 = [
{
id: '1',
name: 'fgr',
data:['p1','p2','p3']
},
{
id: '2',
name: 'gone',
data:['p1','p2','p3']
}
]
output: {
id: '1',
name: 'ron',
data: ['p1']
},
{
id: '2',
name: 'lon',
data: ['p2']
}
With the below assumption:
array1 has objects each of which always have prop data which is an array
array2 also has objects and these objects will always have prop data which is an array as well
the desired objective is to generate an array with objects from array1 whose data have at least one common element with any of the data of array2 objects.
this solution may work:
const findNeedleInHaystack = (needle = array1, haystack = array2) => (
needle.reduce((acc, itm) => (
haystack
.map(x => x.data)
.flat()
.some(x => itm.data.includes(x))
? [...acc, itm]
: [...acc]
), [])
);
Explanation
iterate over the needle array (ie, array1) using .reduce
use .map on haystack array (ie, array2) to separate the data from each object
use .flat() to transform the 2-dimensional resulting array into 1-dimension
use .some to see if any element in the 1-dimensional array is also present in itm's data array
if found, concatenate itm to the result (using ... spread operator on acc - the aggregator/accumulator)
if not found, skip concatenating itm.
Code Snippet
const array1 = [
{
id: '1',
name: 'ron',
data: ['p1']
},
{
id: '2',
name: 'lon',
data: ['p2']
},
{
id: '3',
name: 'voon',
data: ['p4']
}
];
const array2 = [
{
id: '1',
name: 'fgr',
data:['p1','p2','p3']
},
{
id: '2',
name: 'gone',
data:['p1','p2','p3']
}
];
const findNeedleInHaystack = (needle = array1, haystack = array2) => (
needle.reduce((acc, itm) => (
haystack.map(x => x.data).flat().some(x => itm.data.includes(x))
? [...acc, itm]
: [...acc]
), [])
);
console.log(findNeedleInHaystack());

Function question, angular typescript, match id values from 2 arrays to get the index from one of them

I have a two arrays, and I want to match their ID values and then get the index of that id in the second array. I know this sounds simple but I'm super new to the syntax, and I'm having a hard time picturing what this should look like. can anyone model that in simple terms?
example functionality:
var array1 = { id:2, name: 'preston'}
array2 = {
{
id: 1
name: 'john'
},
{
id: 2
name: 'bob'
}
Expected behavior
where both ids = 2, give index of array2.
returns 1
can anyone show me?
You can use findIndex on array2
Try this:
var array1 = {
id: 2,
name: 'preston'
}
var array2 = [{
id: 1,
name: 'john'
},
{
id: 2,
name: 'bob'
}
]
console.log(array2.findIndex(item => item.id === array1.id))
Or use indexOf with map if you want support for IE as well without polyfills.
var array1 = {
id: 2,
name: 'preston'
}
var array2 = [{
id: 1,
name: 'john'
},
{
id: 2,
name: 'bob'
}
]
console.log(array2.map(item => item.id).indexOf(array1.id))
Iterate over each item in array1 using forEach(). Find each item's index in array2 using findIndex().
var array1 = [{id:2, name: "preston"}];
var array2 = [{id: 1, name: "john" }, {id: 2, name: "bob" }];
array1.forEach(item => {
let index = array2.findIndex(obj => obj.id === item.id);
console.log(index);
});

Remove object from array based on array of some property of that object

I have an array of objects (objList) that each has "id" property.
I have an array of strings (idsToRemove), representing IDs of the objects to remove from objList.
I find some solution but I fear it's slow, especially with the large list of objects with lots of properties.
Is there more efficient way to do this?
var idsToRemove = ["3", "1"];
var objList = [{
id: "1",
name: "aaa"
},
{
id: "2",
name: "bbb"
},
{
id: "3",
name: "ccc"
}
];
for (var i = 0, len = idsToRemove.length; i < len; i++) {
objList = objList.filter(o => o.id != idsToRemove[i]);
}
console.log(objList);
Turn the idsToRemove into a Set so that you can use Set.prototype.has (an O(1) operation), and .filter the objList just once, so that the overall complexity is O(n) (and you only iterate over the possibly-huge objList once):
var idsToRemove = ["3", "1"];
var objList = [{
id: "1",
name: "aaa"
},
{
id: "2",
name: "bbb"
},
{
id: "3",
name: "ccc"
}
];
const set = new Set(idsToRemove);
const filtered = objList.filter(({ id }) => !set.has(id));
console.log(filtered);
Note that Array.prototype.includes and Array.prototype.indexOf operations are O(N), not O(1), so if you use them instead of a Set, they may take significantly longer.
You can use Array.includes which check if the given string exists in the given array and combine it with an Array.filter.
const idsToRemove = ['3', '1'];
const objList = [{
id: '1',
name: 'aaa',
},
{
id: '2',
name: 'bbb',
},
{
id: '3',
name: 'ccc',
},
];
const filteredObjList = objList.filter(x => !idsToRemove.includes(x.id));
console.log(filteredObjList);
You don't need two nested iterators if you use a built-in lookup function
objList = objList.filter(o => idsToRemove.indexOf(o.id) < 0);
Documentation:
Array.prototype.indexOf()
Array.prototype.includes()
Simply use Array.filter()
const idsToRemove = ['3', '1'];
const objList = [{
id: '1',
name: 'aaa',
},
{
id: '2',
name: 'bbb',
},
{
id: '3',
name: 'ccc',
}
];
const res = objList.filter(value => !idsToRemove.includes(value.id));
console.log("result",res);

group array by groups and sort by position

I have an array. I need to group this array by groups and sort by position. I tied to create a new array with group names as keys and values as sorted array grouped by group, but didn't work well. How can I do this?
a = [
{id:1,name:'qw'group:'C',name:'hite',position:'1'},
{id:2,name:'qwe'group:'B',name:'ite',position:'2'},
{id:3,name:'qwer'group:'A',name:'ite',position:'3'},
{id:4,name:'qer'group:'D',name:'te',position:'4'},
{id:5,name:'wer'group:'C',name:'whit',position:'5'},
{id:6,name:'er'group:'B',name:'whi',position:'6'},
]
function groupDo(array){
var groups = [];
for (var i in array){
groups[array[i].group] = array[i].group;
}
for (var i in array){
if (groups[array[i].group] == array[i].group){
groups[array[i].group] = array[i];
}
}
}
Here's a simple straight forward answer:
var sortByPosition = function(obj1, obj2) {
return obj1.position - obj2.position;
};
var arr = [
{ id: 1, name: 'qw', group: 'C', name: 'hite', position: '1' },
{ id: 2, name: 'qwe', group: 'B', name: 'ite', position: '2' },
{ id: 3, name: 'qwer', group: 'A', name: 'ite', position: '3' },
{ id: 4, name: 'qer', group: 'D', name: 'te', position: '4' },
{ id: 5, name: 'wer', group: 'C', name: 'whit', position: '5' },
{ id: 6, name: 'er', group: 'B', name: 'whi', position: '6' },
];
var grouped = {};
for (var i = 0; i < arr.length; i += 1) {
if(!grouped[arr[i].group]) {
grouped[arr[i].group] = [];
}
grouped[arr[i].group].push(arr[i]);
}
for (var group in grouped) {
grouped[group] = grouped[group].sort(sortByPosition);
}
console.log(grouped);
When you want to do stuff like this though, it's usually recommended to use a utility library like lodash or underscore.js, so that you don't have to "reinvent the wheel". Here's how it would look like using one of these libraries:
var arr = [
{ id: 1, name: 'qw', group: 'C', name: 'hite', position: '1' },
{ id: 2, name: 'qwe', group: 'B', name: 'ite', position: '2' },
{ id: 3, name: 'qwer', group: 'A', name: 'ite', position: '3' },
{ id: 4, name: 'qer', group: 'D', name: 'te', position: '4' },
{ id: 5, name: 'wer', group: 'C', name: 'whit', position: '5' },
{ id: 6, name: 'er', group: 'B', name: 'whi', position: '6' },
];
var grouped = _.groupBy(arr, 'group');
for (var group in grouped) {
_.sortBy(grouped[group], 'position');
}
console.log(grouped);
Here ya go!
a = [
{id:1,name:'qw',group:'C',name:'hite',position:'1'},
{id:2,name:'qwe',group:'B',name:'ite',position:'2'},
{id:3,name:'qwer',group:'A',name:'ite',position:'3'},
{id:4,name:'qer',group:'D',name:'te',position:'4'},
{id:5,name:'wer',group:'C',name:'whit',position:'5'},
{id:6,name:'er',group:'B',name:'whi',position:'6'},
]
function groupAndSort(array, groupField, sortField) {
var groups = {}; // This object will end being keyed by groups, and elements will be arrays of the rows within the given array, which have been sorted by the sortField
// Put all the rows into groups
for (var i = 0; i < array.length; i++) {
var row = array[i];
var groupValue = row[groupField];
groups[groupValue] = groups[groupValue] || [];
groups[groupValue].push(row);
}
// Sort each group
for (var groupValue in groups) {
groups[groupValue] = groups[groupValue].sort(function(a, b) {
return a[sortField] - b[sortField];
});
}
// Return the results
return groups;
}
var groupedAndSorted = groupAndSort(a, "group", "position");
If you want to group objects, first think about what the resulting data would look like. Maybe something like this?
var grouped = {
A : [
{id:3,name:'qwer', group:'A',name:'ite',position:'3'}
],
B : [],
C : [],
D : []
};
And so on. To transform a list into an object, consider using .reduce().
.reduce() takes a function as its first argument, and a resulting object as the second. The function iterates through each element of the array and reduces it into the given object.
var data = [
{id:1,name:'qw', group:'C',name:'hite',position:'1'},
{id:2,name:'qwe', group:'B',name:'ite',position:'2'},
{id:3,name:'qwer', group:'A',name:'ite',position:'3'},
{id:4,name:'qer', group:'D',name:'te',position:'4'},
{id:5,name:'wer', group:'C',name:'whit',position:'5'},
{id:6,name:'er', group:'B',name:'whi',position:'6'},
]
// acc is the accumulated object, x is each element of the array
data.reduce(function(acc, x) {
// first check if the given group is in the object
acc[x.group] = acc[x.group] ? acc[x.group].concat(x) : [x];
return acc;
}, {}); // this is the resulting object
Now all you need to do is use the built in sort to order the resulting arrays. You could do this by iterating through the keys of the resulting object and applying .sort() to each array. .sort() takes a function as an argument which accesses the data and provides a comparison function.
// a and b are elements of the array
array.sort(function(a, b) {
if (a.position > b.position) {
return -1;
} else if (b.position > a.position) {
return 1;
} else {
return 0;
}
});
And you would implement it like so
var result = Object.keys(data).map(function(d){
return d.sort(f); // f is the function above
});

Categories