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])));
Related
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.
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);
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.
I have nested array of objects that looks like this:
const nestedArray = [
[{ id: 1 }, { id: 2 }, { id: 3 }],
[{ id: 1 }, { id: 2 }],
[{ id: 4 }, { id: 5 }, { id: 6 }],
]
Since objects with id 1 and 2 are already together in nestedArray's first element I want to remove the second element and maintain other elements without petition as they are. The result should be like this:
const nestedArray = [
[{ id: 1 }, { id: 2 }, { id: 3 }],
[{ id: 4 }, { id: 5 }, { id: 6 }]
]
How do I write a filter function by id to get the expected result?
As I see in your example:
the id are unique in each subarray
duplicate sub-array elements only exist in the previous sub-array
if the first element of a sub-array exists in the previous sub-array then all the other elements must also be
const nestedArray =
[ [ { id: 1} , { id: 2} , { id: 3} ]
, [ { id: 1} , { id: 2} ]
, [ { id: 4} , { id: 5} , { id: 6} ]
]
function arrCleaning(arr)
{
for (let i=arr.length;i--;)
{
if (i>0 && arr[i-1].some(x=>x.id===arr[i][0].id) )
arr.splice(i,1)
}
}
arrCleaning( nestedArray )
// result
console.log( 'nestedArray = [' )
nestedArray.forEach(e=>console.log(' ',JSON.stringify(e).replaceAll('"',''),','))
console.log(' ]')
.as-console-wrapper { max-height: 100% !important; top: 0; }
.as-console-row::after { display:none !important; }
Try this:
const nestedArray = [
[{ id: 1 }, { id: 2 }, { id: 3 }],
[{ id: 1 }, { id: 2 }]
]
var newArr = nestedArray.flat(2).filter((x, index, self) => index === self.findIndex((t) => (t.id === x.id)));
console.log(newArr);
In looking for a way to efficiently determine the lowest positive integer that is not used as a value of a specific property in any object within an array of objects.
In other words, I'm looking for a function/algorithm that for these arrays:
var example1 = [{ id: 1 }, { id: 2 }, { id: 3 }],
example2 = [{ id: 6 }, { id: 4 }, { id: 2 }],
example3 = [{ id: 2 }, { id: 1 }, { id: 4, otherProp: 3 }];
Would return 4, 1 and 3 respectively. (Obviously using the id-property, in this example.)
I considered using Underscore.js for this, but I couldn't find a way to do it without some ugly nested loops. Does anybody have a better idea?
function next(prop) {
return function(arr) {
var used = arr.reduce(function(o, v) {
o[v[prop]] = true;
return o;
}, {});
for (var i=1; used[i]; i++);
return i;
}
}
var nextId = next("id");
nextId([{ id: 1 }, { id: 2 }, { id: 3 }]) // 4
nextId([{ id: 6 }, { id: 4 }, { id: 2 }]) // 1
nextId([{ id: 2 }, { id: 1 }, { id: 4, otherProp: 3 }]) // 3
One possible approach:
function aiKey(arr, prop) {
var indices = [];
arr.forEach(function(el) {
indices[ el[prop] ] = true;
});
for (var i = 1, l = indices.length; i < l; i++) {
if (indices[i] === undefined) {
break;
}
}
return i;
}
aiKey(example1, 'id'); // 4
aiKey(example2, 'id'); // 1
aiKey(example3, 'id'); // 3