Remove from array if parent property key has been removed - javascript

I am currently trying to remove from an array of objects if the parent gets removed.
My array looks like this:
const items = [
{
id: '1'
generatedFrom: undefined
},
{
id: '2',
generatedFrom: '1',
},
{
id: '3',
generatedFrom: '2'
},
{
id: '4',
generatedFrom: '1'
}
]
I have a method which filters the objects out based on the id which I am passing into the method:
const removeFromArray = (id: string) => {
const filtered = items.filter(item => item.id !== id);
}
This gives me the correct results which I was hoping for in terms of removing the parent object. However, the problem that I am getting is this should have a knock on effect for the remainder of the objects within the array.
If you take the example above:
If I remove id 2 then it should delete items[1]
As items[2] is generated from id 2 this one should also be removed.
items[3] should stay existing as the parent is still there.
This could be a array which has many items within and I am unsure how to approach it.
Example of current issue:
const items = [
{
id: 1,
generatedFrom: undefined
},
{
id: 2,
generatedFrom: 1
},
{
id: 3,
generatedFrom: 2
},
{
id: 4,
generatedFrom: 3
},
{
id: 5,
generatedFrom: 4
},
{
id: 6,
generatedFrom: 1
}
]
const removeFromArray = (id) => {
return items.filter(item => item.id !== id && item.generatedFrom !== id);
}
console.log(removeFromArray(2));
// Expected Output:
const expected = [
{
id: 1,
generatedFrom: undefined
},
{
id: 6,
generatedFrom: 1
}
]

Since theoretically you could have unlimited level of depth, I don't think one Array.prototype.* call is sufficient here. A beginner's approach would be recursion, but you can tweak that into a queue process:
const items = [
{
id: 1,
generatedFrom: undefined
},
{
id: 2,
generatedFrom: 1
},
{
id: 3,
generatedFrom: 2
},
{
id: 4,
generatedFrom: 3
},
{
id: 5,
generatedFrom: 4
},
{
id: 6,
generatedFrom: 1
}
];
const removeFromArray=(id:number)=>{
let queue:number[]=[id];
let rst=[...items];
while(queue.length>0){
let hd=queue.shift();
rst=rst.filter((item)=>{
if(item.id==hd){
return false;
}
if(item.generatedFrom==hd){
queue.push(item.id);
return false;
}
return true;
});
}
return rst;
};
console.log(removeFromArray(2));
Online playground
Removing 2 will give you
[{
"id": 1,
"generatedFrom": undefined
}, {
"id": 6,
"generatedFrom": 1
}]

You can use a recursive function, I advise you to use a Set in order to avoid infinite loops or repeating deletions:
const items = [
{
id: 1,
generatedFrom: undefined
},
{
id: 2,
generatedFrom: 1
},
{
id: 3,
generatedFrom: 2
},
{
id: 4,
generatedFrom: 3
},
{
id: 5,
generatedFrom: 4
},
{
id: 6,
generatedFrom: 1
}
]
const ids = new Set();
const deleteById = (id, items) => {
ids.delete(id);
let filtered = items.filter(item => {
if (item.id === id || item.generatedFrom === id) {
if(item.id) ids.add(item.id);
return false;
}
return true;
});
const it = ids.values();
let next = it.next();
while (!next.done) {
const value = next.value;
filtered = deleteById(value, filtered);
next = it.next();
}
return filtered;
}
console.log(deleteById(2, items));

I believe you should work on the array object itself instead of multiple loops.
Anyway, you can use recursive method to remove the parent id which is same as removed generatedId. Its short and easy.
See the Snippet below:
const items = [
{
id: 1,
generatedFrom: undefined
},
{
id: 2,
generatedFrom: 1
},
{
id: 3,
generatedFrom: 2
},
{
id: 4,
generatedFrom: 3
},
{
id: 5,
generatedFrom: 4
},
{
id: 6,
generatedFrom: 1
}
]
const removeFromArray = (removeId, items) => {
items = items.filter(item => item.id !== removeId);
let ele = items.find(item=> item.generatedFrom == removeId);
if(ele){
return removeFromArray(ele.id, items);
}else{
return items;
}
}
console.log(removeFromArray(2, items));
// Expected Output:
const expected = [
{
id: 1,
generatedFrom: undefined
},
{
id: 6,
generatedFrom: 1
}
]

You could try with a recursion:
const items = [ { id: 1, generatedFrom: undefined }, { id: 2, generatedFrom: 1 }, { id: 3, generatedFrom: 2 }, { id: 4, generatedFrom: 3 }, { id: 5, generatedFrom: 4 }, { id: 6, generatedFrom: 1 } ]
const removeFromArray = (id, items) => {
items = items.filter(x => x.id !== id); // remove element with id == id
// recursively find orphan objects
let index = items.findIndex(obj => obj.generatedFrom === id);
if (index !== -1) {
let newId = items[index].id;
items = items.filter(x => x.generatedFrom !== id);
return removeFromArray(newId, items)
}
else return items;
}
console.log(removeFromArray(2, items));
Two steps:
remove item with id === id;
recursively find all the objects that was created from the original id and recall the function by filtering result array.

Related

How can add properties to my objects based on the duplicates inside the array?

I have this array of objects
let arr = [
{
id: 1,
},
{
id: 1,
},
{
id: 2,
},
{
id: 1,
},
{
id:4,
},
{
id: 3,
},
{
id:4,
}
]
i need to find and change every object in the array based on condition.
So if there are duplicates in the array i need to set on my objects 100 except last duplicate where i should have 200.
If i don't have any duplicates than i should have again 200
So the output shpuld be
let arr = [
{
id: 1,
number: 100
},
{
id: 1,
number: 100
},
{
id: 2,
number: 200
},
{
id: 1,
number: 200
},
{
id:4,
number: 100
},
{
id: 3,
number: 200
},
{
id:4,
number: 200
}
]
so id 1 has duplicates.
That is why the fiurst occurences are set with number:100 and the last one i set with number:200.
Id 2 has number 200 because there are no duplicates and it is first occurance in the list.
what i tried
I got stuck at
for(let item of arr) {
for(let item2 of arr) {
if(item.id === item2.id) {
item.number = 100;
} else {
item.number = 200;
}
}
}
You can simply iterate through the array in reverse and track which ids you've seen, here using a Set.
const arr = [{ id: 1, }, { id: 1, }, { id: 2, }, { id: 1, }, { id: 4, }, { id: 3, }, { id: 4, }]
let i = arr.length;
const seen = new Set();
while (i--) {
arr[i].number = seen.has(arr[i].id) ? 100 : 200;
seen.add(arr[i].id)
}
console.log(arr)
You can use array.map() to iterate over your array. I think it can provide a nice and concise solution:
const result = arr.map((item, index) => {
const duplicate = arr.filter((_, indx) => indx > index).some((i) => i.id === item.id);
return { ...item, number: duplicate ? 100 : 200 }
});
console.log(result);
We can simply achieve it via Array.map() along with Array.indexOf() & Array.lastIndexOf() methods.
Working Demo :
// Input array
let arr = [{
id: 1,
}, {
id: 1,
}, {
id: 2,
}, {
id: 1,
}, {
id:4,
}, {
id: 3,
}, {
id:4,
}];
// Getting ID's from each object and create a seperate array
let idArr = arr.map(function(item) { return item.id });
// Iterating through the id's array and assigning number property to an original array as per the requirement.
idArr.forEach((item, index) => {
if (idArr.indexOf(item) === idArr.lastIndexOf(item)) {
arr[index].number = 200;
} else {
arr[index].number = 100;
arr[idArr.lastIndexOf(item)].number = 200;
}
});
// Result
console.log(arr);

Flattern object in nested array of arrays Javascript

I have an array of arrays, which contain objects, would like to get the value of a certain key and return it as a big array, have tried a nested map but it returns multiple array's rather than a single array.
const items = [
{
id: 1,
sub_items: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
},
{
id: 2,
sub_items: [
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
}
]
const subItemIDs = items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
)
console.log(subItemIDs);
Expected output
[1, 2, 3, 4, 5, 6]
Actual output
[ [1,2,3], [4,5,6] ]
You can use arrays.flat(). I can provide more specific code once output is mentioned in the question
const arr1 = [0, 1, 2, [3, 4]];
console.log(arr1.flat());
// expected output: [0, 1, 2, 3, 4]
const arr2 = [0, 1, 2, [[[3, 4]]]];
console.log(arr2.flat(2));
// expected output: [0, 1, 2, [3, 4]]
You could take Array#flatMap to get a flat array from nested arrays.
const
items = [{ id: 1, sub_items: [{ id: 1 }, { id: 2 }, { id: 3 }] }, { id: 2, sub_items: [{ id: 4 }, { id: 5 }, { id: 6 }] }],
subItemIDs = items.flatMap(({ sub_items }) => sub_items.map(({ id }) => id));
console.log(subItemIDs);
Achieved this with:
const items = [
{
id: 1,
sub_items: [
{
id: 1
},
{
id: 2
},
{
id: 3
}
]
},
{
id: 2,
sub_items: [
{
id: 4
},
{
id: 5
},
{
id: 6
}
]
}
]
const subItemIDs = [].concat(...items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
))
console.log(subItemIDs);
Sometimes, the obvious is the easiest:
Given a data structure that looks like this
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
A trivial function like this
function extract_item_ids( items ) {
const ids = [];
for ( const item of items ) {
for ( const {id} of sub_items ) {
ids.push(id);
}
}
return ids;
}
should do the trick. If you want to collect the ids from a tree of any depth, it's just as easy:
function extract_item_ids( items ) {
const ids = [];
const pending = items;
while ( pending.length > 0 ) {
const item = pending.pop();
ids.push(item.id);
pending.push(...( item.sub_items || [] ) );
}
return ids;
}
And collecting the set of discrete item IDs is no more difficult:
If you want to collect the ids from a tree of any depth, it's just as easy:
function extract_item_ids( items ) {
const ids = new Set();
const pending = [...items];
while ( pending.length > 0 ) {
const item = pending.pop();
ids.add(item.id);
pending.push(...( item.sub_items || [] ) );
}
return Array.from(ids);
}
As is the case with most things JavaScript, you have several options. Some are more efficient than others, others have a certain stylistic purity, others might better speak to your fancy. Here are a few:
Array.flat
With array flat you can take your original code and have the JS Engine flatten the array down to a one-dimensional array. Simply append .flat() onto the end of your map.
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
const subItemIds = items.map( (item) =>
item.sub_items.map( (subItem) => subItem.id )
).flat()
console.log(subItemIds);
Array.reduce
Another method is to use reduce to iterate over the object and build an accumulation array using Array.reduce. In the example below, when pushing onto the array, the spread operator (...) is used to break the array into elements.
const items = [
{ id: 1, sub_items: [ { id: 1 }, { id: 2 }, { id: 3 }, ] },
{ id: 2, sub_items: [ { id: 4 }, { id: 5 }, { id: 6 }, ] },
];
const subItemIds = items.reduce((arr,item) => (
arr.push(...item.sub_items.map((subItem) => subItem.id)), arr
),[])
console.log(subItemIds);
Other
Other answers here make use of custom functions or Array.flatMap, which should be explored as they could lead to more readable and efficient code, depending on the program's needs.

How to convert `pid` in json array into array of `children` form?

Raw array:
const data1 = [
{
id: 1,
pid: 0
},
{
id: 2,
pid: 1
},
{
id: 3,
pid: 2
}
]
How to convert pid in json array into array of children form?
How to turn him into:
[
{
id: 1,
pid: 0,
children: [
{
id: 2,
pid: 1,
children: [
{
id: 3,
pid: 2
}
]
}
]
}
]
-----------------------------------
He recognizes children by pid
How to write a function to do it?
thanks
const data = [
{
id: 1,
pid: 0
},
{
id: 4,
pid: 3
},
{
id: 2,
pid: 1
},
{
id: 3,
pid: 2
}
];
function toTree (data) {
data.forEach(function(item) {
delete item.children;
});
const map = {};
data.forEach(function(item) {
map[item.id] = item;
});
let val = [];
data.forEach(function(item) {
const parent = map[item.pid];
if(parent) {
(parent.children || (parent.children = [])).push(item);
} else {
val.push(item);
}
});
return val;
}
console.log(JSON.stringify(toTree(data)));
Refer to #chiliNUT answer, add a method :
const data1 = [
{
id: 1,
pid: 0
},
{
id: 4,
pid: 2
},
{
id: 5,
pid: 1
},
{
id: 3,
pid: 2
},
{
id: 2,
pid: 1
}
];
function toTree (data){
data.sort((a, b) => (a.pid - b.pid === 0) ? a.id - b.id : a.pid - b.pid);
const map = {}
data.forEach(item => (map[item.pid] || (map[item.pid] = []) ).push(item))
const mapArr = Object.values(map)
mapArr.reduce((a, b, index, arr) => {
if ( a[0].id === b[0].pid) { // There are still bugs here
a[0].children = b
}
return b;
})
return mapArr[0]
}
console.log(JSON.stringify(toTree(data1)));
data1.reduce((el1, el2)=>{el1.children = [el2]; return el2;});
const tree = [data1[0]];
You can use Array.reduce(el1, el2) It iterates over the array like map, except: For the first iteration, el1 and el2 are the first and second elements of the array, then for the iterations after, el1 is return value of the previous iteration, and el2 is the next element of the array. Unlike map, which operates on each element of the array, reduce uses each element of the array to generate a single return value.
data1.reduce((el1, el2)=>{el1.children = [el2]; return el2;});
So that appends all elements of data1 successively to the first element. Your final output should be an array, so
const tree = [data1[0]]
Follow up: if the data is not already sorted by id, you can sort it like this
data1.sort((el1, el2) => {return el1.id > el2.id ? 1 : -1});
const data1 = [
{
id: 1,
pid: 0
},
{
id: 2,
pid: 1
},
{
id: 3,
pid: 2
}
]
data1.reduce((a,b)=>{a.children=[b];return b;});
const tree = [data1[0]];
console.log(tree);
I think the best way is to use recursive to loop on each element and put as child of the previous one.
const data1 = [
{
id: 1,
pid: 0
},
{
id: 2,
pid: 1
},
{
id: 3,
pid: 2
}
];
function convert(arr){
let counter = 0;
let convertedArray = [];
function recursiveFunction(currentObject = null){
if(counter >= arr.length) return convertedArray;
if(currentObject == null){
currentObject = {
children: [arr[0]]
}
convertedArray.push(currentObject);
} else {
currentObject.children = [ arr[counter] ];
}
counter++;
return recursiveFunction(currentObject.children[0]);
}
return recursiveFunction();
}
let newData = convert(data1);
console.log(newData);

How to compare two array with object?

I compare id with two array with object.
Here is my function:
array1 = [
{ id: 1 },
{ id: 2 },
{ id: 3 }
];
array2 = [
{ id: 1 },
{ id: 2 },
{ id: 3 }
];
const compareFunction = (array1, array2) => {
array2.map((allValue) => {
array1.map((value) => {
allValue.selected = value.id === allValue.id;
});
})
return array2;
}
I think I will get the array2 like
[{ id: 1, selected: true }, { id: 2, selected: true },{ id: 3, selected: true }]
but actually array2 become
[{ id: 1, selected: false }, { id: 2, selected: false },{ id: 3, selected: true }]
Only the last array argument selected become true.
Which step was wrong ? Thanks.
Convert the 2nd array to a Set of id values. Iterate the 1st array with a Array.map() and create a new object for each item, by spreading the current object, and adding the selected value. To get the selected value check if the Set contains that current item id.
const array1 = [{ id: 1 },{ id: 2 },{ id: 3 }];
const array2 = [{ id: 1 },{ id: 2 },{ id: 3 }];
const a2Set = new Set(array2.map(o => o.id))
const result = array1.map(o => ({ ...o, selected: a2Set.has(o.id) }))
console.log(result)
checkout this :
array1 = [{ id: 1 },{ id: 2 },{ id: 3 }];
array2 = [{ id: 1 },{ id: 2 },{ id: 3 }];
const compareFunction = (array1, array2) => {
const result = [];
array2.forEach(arr2item => {
let selected = false;
for(let arr1item of array1){
selected = arr1item.id === arr2item.id;
if(selected)break;
}
result.push({id : arr2item.id , selected : selected});
});
return result;
}
console.log(compareFunction(array1 , array2));

Move selected array index to a specified location

I'm trying to reorder a selected number of array base on selected ids. For example, I have an array of:
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
And a selected id of 1,2,4. The proper arrangement should be:
[ { id: 3 }, { id: 1 }, { id: 2 }, { id: 4 }, { id: 5 }]
I've managed to get the arrangement to work for one selected id, but when multiple ids are selected it fails on different test cases. These all asume the same input as above.
Input 1: [ 1, 2, 4 ], move to index 1:
[ { id: 3 }, { id: 1 }, { id: 2 }, { id: 4 }, { id: 5 } ]
Input 2: [ 1, 3, 4 ], move to index 1:
[ { id: 2 }, { id: 1 }, { id: 3 }, { id: 4 }, { id: 5 } ]
Input 3: [ 1, 3, 5 ], move to index 1:
[ { id: 2 }, { id: 1 }, { id: 3 }, { id: 5 }, { id: 4 } ]
Input 4: [ 1, 2 ], move to index 0 or 1:
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
Input 5: [ 4, 5 ], move to index 3 or 4:
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ]
/**
* Function to move array
*/
function array_move(arr, old_index, new_index) {
if (new_index >= arr.length) {
var k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
};
/**
* Function to find the index
*/
function findWithAttr(array, attr, value) {
for (var i = 0; i < array.length; i += 1) {
if (array[i][attr] === value) {
return i;
}
}
return -1;
}
/**
* Move array to specified position
*/
function moveToSpecifiedInput(selectedImage, iMoveTo) {
selectedImage.reverse();
selectedImage.forEach(function(aData) {
let old_index = findWithAttr(aImageData, 'id', aData);
let new_index = iMoveTo - 1;
array_move(aImageData, old_index, new_index);
});
}
From the clarification in the comments, I think I understand your problem. This is how I’d go about solving it:
function sortArray(array, sortProp, sortValues, sortIndex) {
const elemsBySortVal = array.reduce((obj, elem, idx) => {
obj[elem[sortProp]] = idx;
return obj;
}, {});
let sortedKeys = sortValues.map(val => elemsBySortVal[val]);
let sortedItems = sortedKeys.map(key => array[key]);
let remainingItems = array.filter((_, idx) => !sortedKeys.includes(idx));
return [
...remainingItems.slice(0, sortIndex),
...sortedItems,
...remainingItems.slice(sortIndex),
];
}
console.log(sortArray(
[ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ],
'id',
[ 1, 2, 4 ],
1,
));
This solution works in three phases:
Phase 1:
This is the most complex part. Here, we take your input array and create a map of sort values against input indices. This is best shown by example:
Input (array):
[ { id: 1 }, { id: 3 }, { id: 5 }, { id: 2 }, { id: 4 } ]
Output (elemsBySortVal):
{
1: 0,
3: 1,
5: 2,
2: 3,
4: 4,
}
Phase 2:
Now we use that map to fetch the indices in the input array of the values passed as the sort values:
Input (sortValues):
[ 1, 2, 4 ]
Output (sortedKeys):
[ 0, 3, 4 ]
This is then mapped to the elements from the input array:
Input (sortedKeys):
[ 0, 3, 4 ]
Output (sortedItems):
[ { id: 1 }, { id: 2 }, { id: 4 } ]
And finally, the remaining items are selected from the input array by using sortedKeys to exclude the already sorted ones:
remainingItems:
[ { id: 3 }, { id: 5 } ]
Note that all operations in phase 2 maintain the order of these arrays, even when elements are removed.
Phase 3:
Now we assemble the output array in 3 parts:
Elements before the sorted section
Elements in the sorted section
Elements after the sorted section
The before and after parts are sliced from remainingItems using sortIndex as a cut point, and the sorted section is simply sortedItems from the previous phase.
Remove the elements that match the IDs from the array, and put them in a new array. Then splice that new array back into the original array.
function move_array_elements(array, ids, new_index) {
let extracted = [];
ids.forEach(id => {
let index = array.findIndex(el => el.id == id);
if (index != -1) {
extracted.push(array[index]);
array.splice(index, 1);
}
});
array.splice(new_index, 0, ...extracted);
return array;
}
const orig_array = [ { id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 } ];
console.log(move_array_elements(orig_array, [1, 2, 4], 1));
You can use filter and splice
Please check the comments
let arr=[{id:1}, {id:2}, {id:3}, {id:4}, {id:5}];
let selected=[1,2,4];
let move_2_index=1;
//filter out selected
const selectedonly = arr.filter(a=>selected.includes(a.id));
//filter out not selected
const notselected = arr.filter(a=>!selected.includes(a.id));
//splice to insert selected on not selectd
notselected.splice(move_2_index, 0, selectedonly);
//flattern the array
final_array=notselected.flat();
console.log(final_array);
Using null as a Placeholder
Details in demo.
Demo
let arr=[{id:1},{id:2},{id:3},{id:4},{id:5}];
Array.prototype.move = function(to, moveFrom) {
// Make a copy of the original array
let that = [...this];
// Declare an empty array
let moveTo = [];
/*
- Compare current index with the numbers in move array (offset)
- Replace each match with a null as a placeholder so that the
indexes of the array is still accurate.
- Place each match into the empty array from previous step.
*/
that.forEach(function(obj, idx, arr) {
if (moveFrom.indexOf(idx +1) !== -1) {
moveTo.push(arr.splice(idx, 1, null));
}
});
// Remove all nulls
that = that.filter(function(obj) {
return obj !== null;
});
/*
Insert the new moving array into the copied array at the index
indicated in the first parameter.
*/
that.splice(to, 0, moveTo.flat())
return that.flat();
};
console.log(JSON.stringify(arr.move(1, [1, 3, 4])));
console.log(JSON.stringify(arr.move(0, [1, 2, 5])));
console.log(JSON.stringify(arr.move(3, [1, 2, 3])));
console.log(JSON.stringify(arr.move(1, [2, 5])));
console.log(JSON.stringify(arr.move(2, [1, 4])));

Categories

Resources