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)
})
Related
I have an array of objects and trying to extract the objects with a matched value of the array.
const A = [{_id: 'a', name: '1'}, {_id: 'b', name: '2'}, {_id: 'c', name: '3'}] and const B = ['2', '3'] So, I want to match values of Array B to the Array A and get the objects into the Array C like const C = [{_id: 'b', name: '2'}, {_id: 'c', name: '3'}]
const C = A.forEach((list) => {
let key = []
if(list.includes[B]) {
key.push(list)
}
})
I am stuck at here, how can I push those objects to the Array C?
You could filter the array.
const
arrA = [{ _id: 'a', name: '1' }, { _id: 'b', name: '2' }, { _id: 'c', name: '3' }];
arrB = ['2', '3'];
arrC = arrA.filter(({ name }) => arrB.includes(name));
console.log(arrC);
when you say matched value, it seems as if you're trying too match the name value..
if that's the case- this shoould work..
const A = [{_id: 'a', name: '1'},
{_id: 'b', name: '2'},
{_id: 'c', name: '3'}];
const B = ['2', '3'];
const C = [];
A.forEach((item) => {
if(B.filter(x=>x == item.name).length > 0) {
C.push(item)
}
});
I hope I have been helpful
const arrA = [{ _id: 'a', name: '1' }, { _id: 'b', name: '2' }, { _id: 'c', name: '3' }];
const arrB = ['2', '3'];
var arrC = [];
arrB.forEach(element => {
arrA.forEach(element2 => {
if (element === element2.name) {
arrC.push(element2)
}
});
});
console.log(arrC);
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.
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);
I have two arrays, which look like this:
const persons = [
{
id: 1,
name: 'Peter',
job: 'Programmer'
},
{
id: 2,
name: 'Jeff',
job: 'Architect'
},
];
const salaries = [
{
id: 1,
salary: 3000,
departments: ['A', 'B']
},
{
id: 1,
salary: 4000,
departments: ['A', 'C']
},
{
id: 2,
salary: 4000,
departments: ['C', 'D']
}
];
Now I need to somehow merge this arrays to one, so that every id only exists once. Same keys should be replaced, except it is an array, then I want them to add/concat. So the desired result should look something like this:
const result = [
{
id: 1,
name: 'Peter',
job: 'Programmer',
salary: 4000,
departments: ['A', 'B', 'C']
},
{
id: 2,
name: 'Jeff',
job: 'Architect',
salary: 4000,
departments: ['C', 'D']
}
];
I have already tried:
// double id's, arrays get replaced
Object.assign({}, persons, salaries)
// loadsh: double id's, arrays get concatenated
_.mergeWith(persons, salaries, (objValue, srcValue) => {
if (_.isArray(objValue)) {
return objValue.concat(srcValue);
}
});
// gives me a map but replaces arrays
new Map(salaries.map(x => [x.id, x])
Does anyone have an idea how to accomplish this?
You can use map(), filter(), reduce(), Object.assign() and Spread syntax to achieve required result.
DEMO
const persons = [{
id: 1,
name: 'Peter',
job: 'Programmer'
}, {
id: 2,
name: 'Jeff',
job: 'Architect'
}],
salaries = [{
id: 1,
salary: 3000,
departments: ['A', 'B']
}, {
id: 1,
salary: 4000,
departments: ['A', 'C']
}, {
id: 2,
salary: 4000,
departments: ['C', 'D']
}];
let output = persons.map(obj => {
let filter = salaries.filter(v => v.id == obj.id);
if (filter) {
let departments = filter.reduce((r, v) => [...v.departments, ...r], []);
Object.assign(obj, {
salary: filter[filter.length - 1].salary,
departments: departments.filter((item, pos) => departments.indexOf(item) == pos).sort()
});
}
return obj;
});
console.log(output)
You can concat the arrays, than combine all items with the same id using Array.reduce(), and a Map.
to combine objects with the same id, get the object from the Map. Iterate the new Object.entries() with Array.forEach(). Check if existing value is an array, if not assign the value. If it is an array, combine the arrays, and make the items unique using a Set with array spread.
To convert the Map back to an array, you can spread the Map.values() iterator.
const persons = [{"id":1,"name":"Peter","job":"Programmer"},{"id":2,"name":"Jeff","job":"Architect"}];
const salaries = [{"id":1,"salary":3000,"departments":["A","B"]},{"id":1,"salary":4000,"departments":["A","C"]},{"id":2,"salary":4000,"departments":["C","D"]}];
const result = [...persons.concat(salaries)
.reduce((r, o) => {
r.has(o.id) || r.set(o.id, {});
const item = r.get(o.id);
Object.entries(o).forEach(([k, v]) =>
item[k] = Array.isArray(item[k]) ?
[...new Set([...item[k], ...v])] : v
);
return r;
}, new Map()).values()];
console.log(result);
You could use a Map and iterate all properties and check the type for adding unique values to the arrays.
var persons = [{ id: 1, name: 'Peter', job: 'Programmer' }, { id: 2, name: 'Jeff', job: 'Architect' }],
salaries = [{ id: 1, salary: 3000, departments: ['A', 'B'] }, { id: 1, salary: 4000, departments: ['A', 'C'] }, { id: 2, salary: 4000, departments: ['C', 'D'] }],
result = Array.from(
salaries
.reduce(
(m, o) => {
var t = m.get(o.id) || {};
Object.keys(o).forEach(k => {
if (Array.isArray(o[k])) {
t[k] = t[k] || [];
o[k].forEach(v => t[k].includes(v) || t[k].push(v));
} else if (t[k] !== o[k]) {
t[k] = o[k];
}
});
return m;
},
persons.reduce((m, o) => m.set(o.id, Object.assign({}, o)), new Map)
)
.values()
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
How can I compare multiple arrays of objects and add new properties with the number of occurrences an object was found and the array indexes where the object was found?
The object comparison must be made by the name property.
Example:
var arrays = [
[
{
name: 'aa',
value: 1
},
{
name: 'ab',
value: 2
},
{
name: 'ac',
value: 3
},
{
name: 'aa',
value: 1
}
],
[
{
name: 'aa',
value: 1
},
{
name: 'ab',
value: 2
},
],
[
{
name: 'ac',
value: 3
},
{
name: 'aa',
value: 1
}
]
]
After execution the object from the above array should have these properties:
[
[
{
name: 'aa',
value: 1,
occurrences: 3,
where: [0, 1, 2]
},
{
name: 'ab',
value: 2,
occurrences: 2,
where: [0, 1]
},
{
name: 'ac',
value: 3,
occurrences: 2,
where: [0, 2]
},
{
name: 'aa',
value: 1,
occurrences: 3,
where: [0, 1, 2]
}
],
[
{
name: 'aa',
value: 1,
occurrences: 3,
where: [0, 1, 2]
},
{
name: 'ab',
value: 2,
occurrences: 2,
where: [0, 1]
}
],
[
{
name: 'ac',
value: 3,
occurrences: 2,
where: [0, 2]
},
{
name: 'aa',
value: 1,
occurrences: 3,
where: [0, 1, 2]
}
]
]
Basically I want to check if the object with a specific name property exists in the other arrays.
This is the solution that comes in my mind:
1. Loop through the array that has the most objects
2. Loop through each object
3. Loop through the other arrays and apply Array.prototype.find()
But this will take a lot of time since each of my array will have at least 500 objects...
You can use array#reduce to get the number of occurrences and the index of occurrences in an object.
Then, you can modify your object in the arrays by simply using Object.assign() and adding the where and occurrences property.
var arrays = [ [ { name: 'aa', value: 1 }, { name: 'ab', value: 2 }, { name: 'ac', value: 3 }, { name: 'aa', value: 1 } ], [ { name: 'aa', value: 1 }, { name: 'ab', value: 2 }, ], [ { name: 'ac', value: 3 }, { name: 'aa', value: 1 } ] ];
var result = arrays.reduce((res, arr, index) => {
arr.forEach(({name,value}) => {
res[name] = res[name] || {occurrences: 0};
res[name]['where'] = res[name]['where'] || [];
if(!res[name]['where'].includes(index)){
res[name]['where'].push(index);
res[name].occurrences += 1;
}
});
return res;
},{});
arrays.forEach(arr => arr.forEach(obj => Object.assign(obj, result[obj.name])));
console.log(arrays);
This looked like trivial reduce, until i noticed nested arrays ) so it's more like flatten + reduce, with memory.
Code below is doing what you need, just the prop names are short (as i type them on the phone):
let f = (ai, a,v,i,m) => {
if (!a[v.id]) {
a[v.id] = {id: v.id, v: v.name, count: 1, at: [ai]};
} else {
a[v.id].count += 1;
a[v.id].at.push(ai);
}
return a;
};
let r = [[{id: 'aa', value: 42}], [{id: 'ba', value: 11}, {id: 'aa', value: 42}]]
.reduce ((a, v, i) => v.reduce (f.bind (null, i),a), {});
console.log (r);
Code still visits each element in any array only once, so complexity is O(n), and there should not be a problem running it on arrays up to a million of total elements (e.g. 1000 arrays of 1000 elements, or 200 x 5000).