Merge objects with same id in array - javascript

I guess I have a dead simple problem but still didn't find a solution. I have an array which looks like this:
var originalArray = [{
id: 1,
elements: [1, 2]
},
{
id: 1,
elements: [3, 4]
},
{
id: 5,
elements: ['a', 'b']
},
{
id: 5,
elements: ['c', 'd']
}, {
id: 27,
elements: []
}]
I'd like to modify it to look like this (merge by id and join elements):
newArray = [{
id: 1,
elements: [1, 2, 3, 4]
}, {
id: 5,
elements: ['a', 'b', 'c', 'd']
}, {
id: 27,
elements: []
}]
I already had multiple tries but still didn't find an elegant way of doing it.

You can create an object keyed by ID and push elements with the same ID to them, then convert back to an array. This is more efficient than looping through on every iteration for larger arrays:
var originalArray = [{
id: 1,
elements: [1, 2]
},
{
id: 1,
elements: [3, 4]
},
{
id: 5,
elements: ['a', 'b']
},
{
id: 5,
elements: ['c', 'd']
}, {
id: 27,
elements: []
}];
const arrayHashmap = originalArray.reduce((obj, item) => {
obj[item.id] ? obj[item.id].elements.push(...item.elements) : (obj[item.id] = { ...item });
return obj;
}, {});
const mergedArray = Object.values(arrayHashmap);
console.log(mergedArray);

Try this code :
var originalArray = [{
id: 1,
elements: [1, 2]
},
{
id: 1,
elements: [3, 4]
},
{
id: 5,
elements: ['a', 'b']
},
{
id: 5,
elements: ['c', 'd']
}, {
id: 27,
elements: []
}]
var newArray = [];
originalArray.forEach(item => {
var newItem = {id: item.id, elements: []};
originalArray.forEach(innerItem => {
if(innerItem.id == item.id){
newItem.elements = newItem.elements.concat(innerItem.elements);
}
});
newArray.push(newItem);
});
console.log(newArray);
Output :
[{
id: 1,
elements: [1, 2, 3, 4]
}, {
id: 5,
elements: ['a', 'b', 'c', 'd']
}, {
id: 27,
elements: []
}]

You can use Array.prototype.reduce() to create an object with the ids as properties and the Object.values() to get the result:
const originalArray = [{id: 1, elements: [1, 2]}, {id: 1, elements: [3, 4]}, {id: 5, elements: ['a', 'b']}, {id: 5, elements: ['c', 'd']}, {id: 27, elements: []}]
const objIds = originalArray.reduce((a, { id, elements }) => {
a[id] = a[id] || {id, elements: []}
return {...a, ...{[id]: {id, elements: a[id].elements.concat(elements)}}}
}, {})
const result = Object.values(objIds)
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can do this with a reduce function:
[{
id: 1,
elements: [1, 2]
},
{
id: 1,
elements: [3, 4]
},
{
id: 5,
elements: ['a', 'b']
},
{
id: 5,
elements: ['c', 'd']
}, {
id: 27,
elements: []
}].reduce((prev, cur) => {
const index = prev.findIndex(v => v.id === cur.id);
if (index === -1) {
prev.push(cur);
} else {
prev[index].elements.push(...cur.elements);
}
return prev;
}, [])

This will work and is also decently easy to understand.
Firstly we check if the id is already in the newArray or not and we keep memory of this through a boolean outside the loop that we can verify later on.
After this, if the id "space" is empty, we will fill it up, if it isn't then there is already an id there.
Therefore, we need to update their elements. We can do this by firstly, grabbing the object in the new array that corresponds with the duplicate object in the initial array which has the same id.
After this, we simply push each element from the duplicate to the new one.
var originalArray = [{
id: 1,
elements: [1, 2]
},
{
id: 1,
elements: [3, 4]
},
{
id: 5,
elements: ['a', 'b']
},
{
id: 5,
elements: ['c', 'd']
}, {
id: 27,
elements: []
}]
var newArray = [];
for (obj of originalArray) {
var empty = true;
for (newobj of newArray) {
if (obj.id == newobj.id) { empty = false; }
}
if (empty) {
newArray.push({id: obj.id, elements: obj.elements});
} else {
for (newobj of newArray) {
if (newobj.id == obj.id) {
for (o of obj.elements) {
newobj.elements.push(o);
}
}
}
}
}
console.log(newArray);

You could do this using reduce method and Map to store unique values for each id and then create an array using spread syntax ....
var data = [{"id":1,"elements":[1,2]},{"id":1,"elements":[3,4]},{"id":5,"elements":["a","b"]},{"id":5,"elements":["c","d"]},{"id":27,"elements":[]}]
const result = data.reduce((r, {id, elements}) => {
if(r.get(id)) r.get(id).elements.push(...elements);
else r.set(id, {id, elements});
return r;
}, new Map).values();
console.log([...result])

You can use nested for loops to do that.
var originalArray = [{
id: 1,
elements: [1, 2]
},
{
id: 1,
elements: [3, 4]
},
{
id: 5,
elements: ['a', 'b']
},
{
id: 5,
elements: ['c', 'd']
}, {
id: 27,
elements: []
}]
for(let i=0;i<originalArray.length;i++){
let key = originalArray[i].id;
for(let j=i+1;j<originalArray.length;j++){
if(originalArray[j].id == key){
originalArray[i].elements = [...originalArray[i].elements,...originalArray[j].elements];
delete originalArray.splice(j,1);
}
}
}
console.log(originalArray);

Related

Filter complex array by another array

let array1 = [
{
id: 1,
genres: [
{ id: 4, title: "qqqq" },
{ id: 9, title: "zzzz" },
{ id: 8, title: "eeee" },
],
},
{
id: 2,
genres: [
{ id: 2, title: "qwert" },
{ id: 4, title: "asdf" },
{ id: 5, title: "zxxcc" },
],
},
];
let array2 = [6, 8];
I need to filter array1 if genre id exists in array2.
So in output I should have only first element of array1.
How to do that?
You can use a combination of filter, some and includes:
let array1 = [{id:1,genres:[{id:4,title:"qqqq" },{id:9,title:"zzzz"},{id:8,title:"eeee" }]},
{id:2,genres:[{id:2,title:"qwert"},{id:4,title:"asdf"},{id:5,title:"zxxcc"}]}];
let array2 = [6, 8];
let result = array1.filter(({genres}) => genres.some(({id}) => array2.includes(id)));
console.log(result);
Use the filter function.
One way to do it:
let result = array1.filter(el => {
let output = false;
el.genres.forEach( genre => {
if (array2.includes(genre.id))
output = true;
});
return output;
});

Can searching for overlapping elements from one array in a two-dimensional array be simplified to avoid nested for loops?

I have an array of items that I would like to remove from within a nested object array, if present.
var itemsToRemove = [1, 2, 3];
var data = [
{ id: 'a', list: [1, 3, 4, 5] },
{ id: 'b', list: [2, 6, 7] }
];
should update to
data = [
{ id: 'a', list: [4, 5] },
{ id: 'b', list: [6, 7] }
];
I am able to cut it down to two loops (below) instead of three, but I'm wondering if there's any way to simplify it to one loop/unnested loops.
data.forEach(obj => {
var map = {};
obj.list.forEach(el => map[el] = true);
itemsToRemove.forEach(el => if(map[el] { delete map[el] }));
obj.list = Object.keys(map);
});
You could take Array#filter with Array#includes.
const
itemsToRemove = [1, 2, 3],
data = [
{ id: 'a', list: [1, 3, 4, 5] },
{ id: 'b', list: [2, 6, 7] }
];
data.forEach(o => o.list = o.list.filter(v => !itemsToRemove.includes(v)));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you need a more faster approach, you could take a Set.
const
itemsToRemove = [1, 2, 3],
data = [
{ id: 'a', list: [1, 3, 4, 5] },
{ id: 'b', list: [2, 6, 7] }
],
remove = new Set(itemsToRemove);
data.forEach(o => o.list = o.list.filter(v => !remove.has(v)));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to filter an array of object with an array of numbers

Given an array of objects arr1 how can I filter out to a new array the objects that do not have a property equal to any value in the array of numbers arr2
const arr1 = [
{
key: 1,
name: 'Al'
},
{
key: 2,
name: 'Lo'
},
{
key: 3,
name: 'Ye'
}
];
const arr2 = [2, 3]
// Failed attempt
const newArr = arr1.filter(obj1 => arr2.some(num1 => num1 !== obj1.key))
console.log(newArr)
// Expected: [{ key: 1, name: 'Al' }]
// Received: [
// { key: 1, name: 'Al' },
// { key: 2, name: 'Lo' },
// { key: 3, name: 'Ye' }
// ]
Using your syntax:
You have to match on the somein case it's the same and not different. Then if it matches, do not keep the value.
const arr1 = [
{
key: 1,
name: 'Al',
},
{
key: 2,
name: 'Lo',
},
{
key: 3,
name: 'Ye',
},
];
const arr2 = [2, 3];
const newArr= arr1.filter(x => !arr2.some(y => y === x.key));
console.log(newArr);
Alternative syntax below :
const arr1 = [{
key: 1,
name: 'Al',
},
{
key: 2,
name: 'Lo',
},
{
key: 3,
name: 'Ye',
},
];
const arr2 = [2, 3];
const newArr = arr1.filter(({
key,
}) => !arr2.some(y => y === key));
console.log(newArr);
That said, you should be using Array.includes() like some ppl answered. It's simplier for the same outcome
const arr1 = [{
key: 1,
name: 'Al',
},
{
key: 2,
name: 'Lo',
},
{
key: 3,
name: 'Ye',
},
];
const arr2 = [2, 3];
const newArr = arr1.filter(({
key,
}) => !arr2.includes(key));
console.log(newArr);
You can do this
const newArr = arr1.filter(obj => !arr2.includes(obj.key));
This will work for you:
const arr1 = [
{
key: 1,
name: 'Al'
},
{
key: 2,
name: 'Lo'
},
{
key: 3,
name: 'Ye'
}
];
const arr2 = [2, 3]
const filtered = arr1.filter(val => !arr2.includes(val.key))
console.log(filtered)
:)
For situations like this Set is also very cool (and for big arrays more performant):
const arr1 = [
{
key: 1,
name: 'Al'
},
{
key: 2,
name: 'Lo'
},
{
key: 3,
name: 'Ye'
}
];
const arr2 = [2, 3]
const arr2Set = new Set(arr2);
const newArr = arr1.filter(obj1 => !arr2Set.has(obj1.key))
console.log(newArr)
You can use indexOf like this:
const newArr = arr1.filter(obj => arr2.indexOf(obj.key) > -1);
You need to filter the arr1 when arr1 element does not exist in arr2, so I think it could be better to use indexOf() like this
const newArr = arr1.filter(obj1 => arr2.indexOf(obj1.key) === -1)
if the element does not exist in arr2 it will return -1 which what you need.

Getting exact Matching elements between two arrays

I have two arrays as follows:
array1 = [
{id:1, children: ['a', 'b']},
{id:2, children: ['a', 'b']},
{id:3, children: ['b', 'c']},
{id:4, children: ['c', 'a']},
{id:5, children: ['a', 'b', 'c']}];
array2 = ['a', 'b'];
Now I want to write a code in JS/TS which will find the exact objects from array1 where every element of children array from array 2 matches exactly with every elements of children array from array 1 (Order doesn't matter).
I have tried to solve this problem with three filters with additional condition of length matching of children array between array 1 and array2. But this also picks up if at least one element gets matched of those children array with desired array length.
I would really appreciate if someone gives me the solution.
array1
.filter(a => a.children
.filter(b => array2
.filter(c => b === c)).length === array2.length);
Edit:
I had actually simplified the problem a bit in the above example. In my actual project, the the two arrays are as follows:
const productOrders: ProductOrder[] =
[
{
productId: 1, subProductOrders:
[{subProduct: {subProductId: 1}}, {subProduct:
{subProductId: 2}}]
},
{
productId: 1, subProductOrders:
[{subProduct: {subProductId: 2}}, {subProduct:
{subProductId: 1}}]
},
{
productId: 1, subProductOrders:
[{subProduct: {subProductId: 2}}, {subProduct:
{subProductId: 3}}]
},
{
productId: 1, subProductOrders:
[{subProduct: {subProductId: 1}}, {subProduct:
{subProductId: 2}}, {subProduct: {subProductId: 3}}]
},
];
const matchingCriteria: SubProductOrder[] =
[
[{subProduct: {subProductId: 1}}, {subProduct: {subProductId:
2}}]
];
Now I want to find the products from the productOrders array where subProductId of the subProductOrders array matches with the subProductId of the matchingCriteria Array (Order doesn't matter). In the above example, the first two products of the productOrders Array should match despite unordered subProductsIds
You could take a Set and check the children against this structure.
var array1 = [{ id: 1, children: ['a', 'b'] }, { id: 2, children: ['a', 'b'] }, { id: 3, children: ['b', 'c'] }, { id: 4, children: ['c', 'a'] }, { id: 5, children: ['a', 'b', 'c'] }],
array2 = ['a', 'b'],
set2 = new Set(array2),
result = array1.filter(({ children }) =>
children.length === set2.size && children.every(Set.prototype.has, set2));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
For a more complex data structure, you could destructure the needed parts and check against a set of subProductId.
const
getId = ({ subProduct: { subProductId } }) => subProductId;
var productOrders = [{ productId: 1, subProductOrders: [{ subProduct: { subProductId: 1 } }, { subProduct: { subProductId: 2 } }] }, { productId: 1, subProductOrders: [{ subProduct: { subProductId: 2 } }, { subProduct: { subProductId: 1 } }] }, { productId: 1, subProductOrders: [{ subProduct: { subProductId: 2 } }, { subProduct: { subProductId: 3 } }] }, { productId: 1, subProductOrders: [{ subProduct: { subProductId: 1 } }, { subProduct: { subProductId: 2 } }, { subProduct: { subProductId: 3 } }] }],
matchingCriteria = [{ subProduct: { subProductId: 1 } }, { subProduct: { subProductId: 2 } }],
set2 = new Set(matchingCriteria.map(getId)),
result = productOrders.filter(({ subProductOrders }) =>
subProductOrders.length === set2.size &&
subProductOrders.every(o => set2.has(getId(o)))
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Array#every() and length in a single filter
const arr1 = [
{id:1, children: ['a', 'b']},
{id:2, children: ['a', 'b']},
{id:3, children: ['b', 'c']},
{id:4, children: ['c', 'a']},
{id:5, children: ['a', 'b', 'c']}
];
const arr2 = ['a', 'b'];
const matched = arr1.filter(({children: c}) => c.length === arr2.length && arr2.every(v => c.includes(v)))
console.log(matched)
array1 = [
{id:1, children: ['a', 'b']},
{id:2, children: ['b', 'a']},
{id:3, children: ['b', 'c']},
{id:4, children: ['c', 'a']},
{id:5, children: ['a', 'b', 'c']}];
array2 = ['a', 'b'];
const x = array1
.map(a => a.children.sort())
.filter(a => a.length === array2.length)
.map(a => a.sort())
.filter(a => JSON.stringify(a)==JSON.stringify(array2))
console.log(x)
You are filtering array1 elements based on the equality of element.children to array2.
so i encourage you to take a look at these answers. as it discuss the equality of two arrays in javascript.
and modify the following code to your needs, this is just one of the easiest options available:
array1.filter((element,index) => {
return JSON.stringify(element.children) === JSON.stringify(array2)
})

Javascript sort an objects by another array [duplicate]

This question already has answers here:
How do I sort an array of objects based on the ordering of another array?
(9 answers)
Javascript - sort array based on another array
(26 answers)
Closed 4 years ago.
I have two arrays.
itemsArray =
[
{ id: 8, name: 'o'},
{ id: 7, name: 'g'},
{ id: 6, name: 'a'},
{ id: 5, name: 'k'},
{ id: 4, name: 'c'}
]
sortArray = [4,5]
How can i sort itemsArray by sortArray (lodash or pure), but i want to for this:
newArray =
[
{ id: 4, name: 'c'},
{ id: 5, name: 'k'},
{ id: 8, name: 'o'},
{ id: 7, name: 'g'},
{ id: 6, name: 'a'}
]
In a case like this where you want to sort on multiple levels, you need to sort them in descending order of importance inside your sorting function.
In this case we sort regularly on cases where both elements are either in or not in the sorting array.
var itemsArray = [
{ id: 8, name: 'o' },
{ id: 7, name: 'g' },
{ id: 6, name: 'a' },
{ id: 5, name: 'k' },
{ id: 4, name: 'c' }
];
var sortArray = [4, 5];
var sortedItemsArray = itemsArray.sort(function (a, b) {
if (sortArray.includes(a.id) == sortArray.includes(b.id)) { //both or neither are in sort array
return b.id - a.id;
}
else if (sortArray.includes(a.id)) { //only a in sort array
return -1;
}
else { //only b in sort array
return 1;
}
});
console.log(sortedItemsArray);
The above snippet could be expanded in multiple ways, but a popular approach is to separate it into several sorting steps.
var itemsArray = [
{ id: 8, name: 'o' },
{ id: 7, name: 'g' },
{ id: 6, name: 'a' },
{ id: 5, name: 'k' },
{ id: 4, name: 'c' }
];
var sortArray = [4, 5];
function sortId(a, b) {
return b.id - a.id;
}
function sortIdByList(a, b) {
if (sortArray.includes(a.id)) {
return -1;
}
if (sortArray.includes(b.id)) {
return 1;
}
return 0;
}
//TEST
var sortedItemsArray = itemsArray
.sort(sortId)
.sort(sortIdByList);
console.log(sortedItemsArray);
This pattern can be easier to maintain as each step is clearly labeled and the functions can be reused in other sorting cases.
The only downside to this pattern is that you end up iterating over the list multiple times, thus increasing the time to sort. Usually this is a non-issue but on very large lists this can be significant.
Sort by array index only
As the comments points out i misread the question, so my previous two sorting snippets doesn't necessarily give the desired result.
This version sorts only by id index in the sorting array:
var itemsArray = [
{ id: 8, name: 'o' },
{ id: 7, name: 'g' },
{ id: 6, name: 'a' },
{ id: 5, name: 'k' },
{ id: 4, name: 'c' }
];
var sortArray = [4, 5];
//TEST
var sortedItemsArray = itemsArray
.sort(function (a, b) {
//Calculate index value of a
var A = sortArray.indexOf(a.id);
if (A == -1) {
A = sortArray.length;
}
//Calculate index value of b
var B = sortArray.indexOf(b.id);
if (B == -1) {
B = sortArray.length;
}
//Return comparison
return A - B;
});
console.log(sortedItemsArray);
You could take the indices of the array for keeping the relative position and take the special items with a negative index to top for sorting.
Then sort the array by taking the indices.
var array = [{ id: 8, name: 'o' }, { id: 7, name: 'g' }, { id: 6, name: 'a' }, { id: 5, name: 'k' }, { id: 4, name: 'c' }],
sortArray = [4, 5],
indices = array.reduce((r, { id }, i) => (r[id] = i, r), {});
sortArray.forEach((id, i, { length }) => indices[id] = i - length);
array.sort(({ id: a }, { id: b }) => indices[a] - indices[b]);
console.log(array);
console.log(indices);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories