Matching a set of items containing arrays in MongoDB - javascript

Let's say I have the following data
[
{ name: 'A', values: [1, 2, 3 ,4] },
{ name: 'B', values: [5, 6, 7, 8] }
]
and I have a mongodb that has the following items in a collection:
#1 { name: 'A', values: [1, 2, 3 ,4], path: '...' },
#2 { name: 'B', values: [8, 9, 5, 7], path: '...' },
#3 { name: 'B', values: [5, 6, 7, 8], path: '...' },
#4 { name: 'C', values: [9, 10, 11, 12], path: '...' }
Now I want to query the paths that match my 2 items A and B from the data (Items #1 and #3). Is that possible edit: with a single query?

You can loop through the data and use query inside the map function by making it asynchronous
const data = [
{ name: 'A', values: [1, 2, 3 ,4] },
{ name: 'B', values: [5, 6, 7, 8] }
]
const result = []
await Promise.all(data.map(async(d) => {
result.push(await db.collection.findOne(d))
}))
Or even with the single query
await db.collection.find(
{
name: { $in: data.map(({ name }) => name) },
values: { $in: data.map(({ values }) => values) }
}
)

Related

Looping and splitting Object

how to loop on object and split it by ":" into separate one
{
labs: [1,2,":",3,4],
level: [1,2,":",3,4]
}
Expected Output:
{
labs: [1,2],
level: [1,2]
}
{
labs: [3,4],
level : [3,4]
}
You can use itertools.groupby from the standard library to group the numbers that are not ":". With that you can use zip to pair off the groups.
from itertools import groupby
d = {
"labs": [1,2,":",3,4],
"level": [10,20,":",30,40]
}
groups = [[(k, list(g)) for b, g in groupby(v, key=lambda n:n != ':') if b]
for k, v in d.items()
]
list(map(dict, zip(*groups)))
# [
# {'labs': [1, 2], 'level': [10, 20]},
# {'labs': [3, 4], 'level': [30, 40]}
# ]
This should work with arbitrary data. For example with input like:
d = {
"labs": [1,2,":",3,4,":", 5, 6],
"level": [10,20,":",30,40,":",50, 60],
"others":[-1,-2,":",-3,-4,":",-5,-6]
}
You will get:
[{'labs': [1, 2], 'level': [10, 20], 'others': [-1, -2]},
{'labs': [3, 4], 'level': [30, 40], 'others': [-3, -4]},
{'labs': [5, 6], 'level': [50, 60], 'others': [-5, -6]}
]
But it does expect the lists to be the same length because the way zip() works. If that's not a good assumption you will need to decide what to do with uneven lists. In that case itertools.zip_longest() will probably be helpful.
Use javaScript to resolve this problem, maybe the code is not the best
const obj = {
labs: [1,2,":",3,4,":",5,6],
level: [1,2,":",3,4,":",7,8],
others: [1,2,":",3,4,":",9,10]
}
const format = (obj = {}, result = []) => {
const keys = Object.keys(obj);
for ( key of keys) {
const itemValues = obj[key].toString().split(':');
let tempRes = {}
itemValues.map((itemValue, index) => {
Object.assign(tempRes, {
[`${keys[index]}`]: itemValue.replace(/^(,)+|(,)+$/g, '').split(',').map(Number)// you can format value here
})
})
result.push(tempRes);
}
return result;
}
console.log(format(obj))
You will get
[
{ labs: [ 1, 2 ], level: [ 3, 4 ], others: [ 5, 6 ] },
{ labs: [ 1, 2 ], level: [ 3, 4 ], others: [ 7, 8 ] },
{ labs: [ 1, 2 ], level: [ 3, 4 ], others: [ 9, 10 ] }
]

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; }

What is the most efficient way to iterate between two arrays to find matched values?

I need to find objects in array by matching array of ids. Array of ids can be longer or equal to length of array of persons. I made it with forEach loop of persons array and inside used includes method to find matched id but not sure that it is the good approach. Is there a way to optimize searching algorithm?
const ids = [1, 4, 9, 7, 5, 3];
const matchedPersons = [];
const persons = [
{
id: 1,
name: "James"
},
{
id: 2,
name: "Alan"
},
{
id: 3,
name: "Marry"
}
];
persons.forEach((person) => {
if (ids.includes(person.id)) {
matchedPersons.push(person);
}
});
console.log(matchedPersons);
codesanbox
You could take a Set with O(1) for the check.
const
ids = [1, 4, 9, 7, 5, 3],
persons = [{ id: 1, name: "James" }, { id: 2, name: "Alan" }, { id: 3, name: "Marry" }],
idsSet = new Set(ids),
matchedPersons = persons.filter(({ id }) => idsSet.has(id));
console.log(matchedPersons);
you better use filter. it does exactly what it is meant to do:
const ids = [1, 4, 9, 7, 5, 3];
const persons = [
{
id: 1,
name: "James"
},
{
id: 2,
name: "Alan"
},
{
id: 3,
name: "Marry"
}
];
const matchedPersons = persons.filter(({id}) => ids.includes(id))
console.log(matchedPersons)
you can use Map https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
const ids = [1, 4, 9, 7, 5, 3];
const matchedPersons = [];
const persons = [
{
id: 1,
name: "James"
},
{
id: 2,
name: "Alan"
},
{
id: 3,
name: "Marry"
}
];
const personsMap = new Map()
persons.forEach((person) => {
personsMap.set(person.id, person)
});
persons.forEach((person) => {
if (personsMap.has(person.id)) {
matchedPersons.push(personsMap.get(person.id));
}
});
console.log(matchedPersons);

Merge objects with same id in array

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);

Categories