Find matching elements in multi dimensional arrays without too many nested loops? - javascript

I have an array with three levels of nesting and a one dimensional object.I need to compare the two to find matching ID's and push them into a new array as pairs. I'm just using the map method here, but perhaps there's a more efficient way to do this? I've thought of using the filter method, but I don't think it can work in this case.
THE FUNCTION:
const getMatchingIDs = function (pages, storedOBJ) {
const arr = []
pages.map((page)=> {
return page.questions.map((q)=>{
const questionText = q.questionText
return Object.keys(storedOBJ).map((key) => {
const answerIndex = storedOBJ[key]
if (typeof answerIndex !== 'undefined' && questionText === key) {
const answerID = q.answers[answerIndex].id
arr.push( q.id + ':' + answerID)
}
})
})
})
return arr
}
THE ARRAY AND OBJECT:
const pages = [
{
questions: [
{
id: 987,
questionText: 'Some Question',
answers: [
{
id: 154
},
{
id: 232
},
{
id: 312
}
]
},
{
id: 324,
questionText: 'Another Question',
answers: [
{
id: 154
},
{
id: 232
},
{
id: 312
}
]
},
{
id: 467,
questionText: 'Last Question',
answers: [
{
id: 154
},
{
id: 232
},
{
id: 312
}
]
}
]
}
]
const storedOBJ = {
'Some Question': 0,
'Last Question': 0,
'Another Question': 2
}
Running getMatchingIDs(pages, storedOBJ) should return ["987:154", "324:312", "467:154"]

Your use of 'map'
So for one thing, you are using the 'map' method where it would be better to use other array methods such as 'forEach' or 'reduce'. The the function passed in to the 'map' method is supposed to return an element for the new array. You are using the 'map' method just to iterate the arrays without capturing the result.
Example #1
Here is a slightly modified version of your 'matchIDs' function. The first reduce flattens the pages to make a single list of questions. The second reduce produces your matches, and skips conditions where the answer index is undefined.
function matchIDs(pages, answerMap) {
const questions = pages.reduce((questions, page) => { return questions.concat(page.questions) }, []);
return questions.reduce((matches, question) => {
const answerIndex = answerMap[question.questionText];
if(typeof answerIndex != 'undefined') matches.push(`${question.id}:${question.answers[answerIndex].id}`);
return matches;
}, []);
}
Example #2
In your example data you have only one page and all of your answer indexes are valid. If you can make these assumptions you could simplify further:
function matchIDs(questions, answerMap) {
return questions.map(question => {
const answerIndex = answerMap[question.questionText];
return `${question.id}:${question.answers[answerIndex].id}`;
});
}
Runnable snippet
const pages = [
{
questions: [
{
id: 987,
questionText: 'Some Question',
answers: [
{
id: 154
},
{
id: 232
},
{
id: 312
}
]
},
{
id: 324,
questionText: 'Another Question',
answers: [
{
id: 154
},
{
id: 232
},
{
id: 312
}
]
},
{
id: 467,
questionText: 'Last Question',
answers: [
{
id: 154
},
{
id: 232
},
{
id: 312
}
]
}
]
}
];
const storedOBJ = {
'Some Question': 0,
'Last Question': 0,
'Another Question': 2
};
function matchIDs(pages, answerMap) {
const questions = pages.reduce((questions, page) => { return questions.concat(page.questions) }, []);
return questions.reduce((matches, question) => {
const answerIndex = answerMap[question.questionText];
if(typeof answerIndex != 'undefined') matches.push(`${question.id}:${question.answers[answerIndex].id}`);
return matches;
}, []);
}
function matchIDsAlt(questions, answerMap) {
return questions.map(question => {
const answerIndex = answerMap[question.questionText];
return `${question.id}:${question.answers[answerIndex].id}`;
});
}
console.log(matchIDs(pages, storedOBJ));
console.log(matchIDsAlt(pages[0].questions, storedOBJ));

Related

How to return array of objects by comparing two arrays javascript

i have two arrays Here, alreadyAddedAreas is single Array and areasData is a nested Array- Here areasData look like this.Here what i want to return data Like in same structure as Areas Data and add isAdded property if its present in alreadyAddedAreas
const areasData=[
countries: [
{
id: 123,
cities:[
{
id: 001,
areas: [{
id: 890,
}, {
id:891
}]
},
{
id: 002,
areas: [{
id: 897,
}, {
id:899
}]
},
]
}
]
]
//alreadyAddedAreas is an Array of Added Areas
const areas: [{
id: 890,
}, {
id:891
}]
// Here what i want to return data Like in same structure as Areas Data and add isAdded property if its present in alreadyAddedAreas
export const getFilteredAreasList1 = (areasData, alreadyAddedAreas) => {
const filteredArray = [];
if (areasData && alreadyAddedAreas) {
areasData[0]?.cities?.map((city) => {
return city?.areas?.map((area) => {
if (
!alreadyAddedAreas.find((item) => item?.areaID?._id === area?._id)
) {
filteredArray.push({
...area,
isAdded: false,
});
} else {
filteredArray.push({
...area,
isAdded: true,
});
}
});
});
return filteredArray;
}
};

How to search array of objects and push new value in array Angular 8

I have two different response from API. Below response contain lineId and Name.
this.lines = [
{
lineId: "R_X002_ACCESS"
localName: "ACCESS"
name: "ACCESS"
},
{
lineId: "R_X00R_X002_BIB2_ACCESS"
localName: "BIB"
name: "BIB"
},
{
lineId: "R_X002_KNORR"
localName: "Knorr"
name: "Knorr"
},
{
lineId: "R_X002_POWDER"
localName: "Powder"
name: "Powder"
},
];
This response is for processData function, Here i wanted to search name from this.lines api response based on lineId of item object and if matches then need to push Name
item = {
lineId: "R_X002_POWDER"
},
{
lineId: "R_X00R_X002_BIB2_ACCESS,R_X002_ACCESS"
},
{
lineId: "R_X002_POWDER"
};
Now in below code , i am searching name based on lineId from this.lines api response
and if it matches then trying to push inside plist array.
Below is my code, here i am passing api response and preparing array based on some condition.
I tried below code inside processData function, but it is not working for comma seprated valuesand also not pushing to proper plist array.
var lineName = this.lines.filter(function(line) {
if(line.lineId === item.lineId){
return line.name;
}
});
processData(data: any) {
let mappedData = [];
for(const item of data){
console.log(item,"item");
var lineName = this.lines.filter(function(line) {
if(line.lineId === item.lineId){
return line.name;
}
});
const mitem = mappedData.find(obj => obj.makeLineName == item.makeLineName);
if(mitem){
mitem['plist'].push(item);
} else {
let newItem = item;
newItem['plist'] = [ item ];
mappedData.push(newItem);
}
}
return mappedData;
}
Expected output
lineId: "R_X002_POWDER",
name: "Powder"
},
{
lineId: "R_X00R_X002_BIB2_ACCESS,R_X002_ACCESS",
name: "BIB","ACCESS"
},
{
lineId: "R_X002_KNORR",
name: "Knorr"
};
I think because of lineId returned to you, instead of checking lineId equality you should check if the incoming lineId includes your lineId.
in your code: instead of line.lineId === item.lineId check this: (item.lineId).includes(line.lineId)
maybe it works...
Does this work for you(mapNames function)...
const lines = [
{
lineId: "R_X002_ACCESS",
localName: "ACCESS",
name: "ACCESS"
},
{
lineId: "R_X00R_X002_BIB2_ACCESS",
localName: "BIB",
name: "BIB"
},
{
lineId: "R_X002_KNORR",
localName: "Knorr",
name: "Knorr"
},
{
lineId: "R_X002_POWDER",
localName: "Powder",
name: "Powder"
},
];
const items = [
{
lineId: "R_X002_POWDER"
},
{
lineId: "R_X00R_X002_BIB2_ACCESS,R_X002_ACCESS"
},
{
lineId: "R_X002_POWDER"
}
];
function mapNames(lines, items) {
const mappedLines = {};
lines.forEach(lineItem => {
if (!mappedLines[lineItem.lineId]) {
mappedLines[lineItem.lineId] = lineItem;
}
});
const mappedItems = items
.map(item => {
return {
lineId: item.lineId,
name: item.lineId.split(",")
.map(lineItem => mappedLines[lineItem].name || "")
.filter(x => x)
.join(",")
};
});
return mappedItems;
}
console.log("Mapped Names:\n", mapNames(lines, items));

Removing repeating elements in a array based on specific data [Discord.js]

I have a problem here that I can't deal with. There is little written about this on the internet. Well, when starting out, I need to make a function that will remove the duplicate data from the table, but the comparison of this data must be based on the data from the table object, below I will give an example because I do not know if I explained it well.
[
{
id: 1234,
user: {
nickname: 'a' <-
}
},
{
id: 1234,
user: {
nickname: 'b' <-
}
},
{
id: 1234,
user: {
nickname: 'a' <-
}
},
]
Data is to be compared according to user.nickname.
I tried to do it this way
array.filter((value, index) => array.indexOf (value.user.nickname) === index)
but all I got was a blank array
[]
If anyone can help, I will be grateful because I have this situation.
Your approach is wrong. Here's one way you can do it instead:
const mapOfNicknames = {};
array.forEach((e)=> {
const nick = e.user.nickname;
// check if nick already exists in map
if ( !mapOfNicknames[nick] ) {
mapOfNicknames[nick] = e;
}
});
// at this point, mapOfNicknames has a unique list
const uniqueArray = Object.keys(mapOfNicknames).map( k => mapOfNicknames[k] );
Using Array.filter as you try, should be a proper aproat:
const users = [
{
id: 1234,
user: {
nickname: "a",
},
},
{
id: 1234,
user: {
nickname: "b",
},
},
{
id: 1234,
user: {
nickname: "a",
},
},
];
let filteruniquebyUserName = users.filter(
(user, index, users) => users.findIndex((compareUser) => compareUser.user.nickname === user.user.nickname) === index
);
console.log(filteruniquebyUserName);
See: How to remove all duplicates from an array of objects?
Another way a little more extended but easier to understand:
const data = [
{
id: 1234,
user: {
nickname: "a",
},
},
{
id: 1234,
user: {
nickname: "b",
},
},
{
id: 1234,
user: {
nickname: "b",
},
},
{
id: 1234,
user: {
nickname: "a",
},
},
];
let elementRepeated = [];
let filteruniquebyUserName = data.filter((user, index, data) => {
if(elementRepeated.includes(user.user.nickname)) return;
const numberOfRepeatUser = data.filter(element => element.user.nickname === user.user.nickname).length;
if(numberOfRepeatUser > 1) elementRepeated.push(user.user.nickname);
return user
});
console.log(filteruniquebyUserName);
Apparently you can't do an indexOf check in a nested object like you are doing it right now. See: Javascript indexOf on an array of objects

Best way to modify a property in all elements of a nested array of objects

I have an object in the following format
const courses = [
{
degree: 'bsc',
text: 'Some text',
id: 'D001',
},
{
degree: 'beng',
text: 'Some text',
id: 'D002',
},
{
degree: 'bcom',
text: 'Some text',
id: 'D003',
electives: [
{
degree: 'bsc',
course: 'Psychology'
text: 'Some text',
id: 'C010',
},
],
},
];
I would like to transform the text property of all the objects in the array, including the objects in the nested arrays, if any. This is the format I would like to get:
const courses = [
{
degree: 'bsc',
text: translateText('D001'),
id: 'D001',
},
{
degree: 'beng',
text: translateText('D002'),
id: 'D002',
},
{
degree: 'bcom',
text: translateText('D003'),
id: 'D003',
electives: [
{
degree: 'bsc',
course: 'Psychology'
text: translateText('C010'),
id: 'C010',
},
],
},
];
The transformation needs to be done with the Id property passed as a parameter. This is what I have tried:
courses.forEach(course => {
course.text = translateText(course.id);
if (course.degree === 'bcom') {
course.electives.forEach(elective => {
elective.text = translateText(elective.id);
})
}
});
I do not like this approach since it may end up being clogged with a lot of if statements if arrays will be added as properties to more degree types. There may also be a performance cost (not sure if it can perform any better).
Is there a better and cleaner way of achieving the above?
#Nenad beat me to the recursive function, but I'm going to post this answer anyway, because you may want to recurse through any nested objects, not just the electives property. Here's how you could do that:
const courses = [
{
degree: 'bsc',
text: 'Some text',
id: 'D001',
},
{
degree: 'beng',
text: 'Some text',
id: 'D002',
},
{
degree: 'bcom',
text: 'Some text',
id: 'D003',
electives: [
{
degree: 'bsc',
course: 'Psychology'
text: 'Some text',
id: 'C010',
},
],
},
];
const translateTextProperty = function(obj) {
if( typeof obj.text != 'undefined' )
obj.text = translateText(obj.id);
for(let key in obj ) {
if( obj[key] instanceof Object ) {
translateTextProperty( obj[key] );
}
}
}
courses.forEach( c => translateTextProperty(c) );
You could use recursive function that will look for electives key and if the property exists in the object it will update the nested data structure.
const courses = [{"degree":"bsc","text":"Some text","id":"D001"},{"degree":"beng","text":"Some text","id":"D002"},{"degree":"bcom","text":"Some text","id":"D003","electives":[{"degree":"bsc","course":"Psychology","text":"Some text","id":"C010"}]}]
const f = text => text + ' - updated';
const update = data => {
data.forEach(e => {
if (e.text) {
e.text = f(e.text)
}
if (e.electives) {
update(e.electives)
}
})
}
update(courses);
console.log(courses)

sort an array of object accoring to the values

I have this array of objects:
const array =[
{ a: 'good car' },
{ a: 'good car1' },
{ a: 'good car2' },
{ a: 'good car3' },
{ a: 'good car4' },
{ b: 'good car1' }
];
I need to sort it via values except in the case of ties. In the case of ties, the key is used to break the tie. I searched A LOT but couldn't use the answers as my keys in objects are not the same and also in case of the tie (values are the same), I cannot handle that.
Any suggestions?
You could get the entries, pick the first one and destructure it to key and value and take the same for the other object, then return the chained sorting values.
var array = [{ v21: 'sad sdd' }, { aaa: 'sad sdd' }, { v11: 'r err rr' }, { hf32: 'erwwer fgh' }, { z3f2: 'agfrr vbbar' }, { t142: 'gggoog anfa' }, { u23: 'fa handle err' }];
array.sort((a, b) => {
var [keyA, valueA] = Object.entries(a)[0],
[keyB, valueB] = Object.entries(b)[0];
return valueA.localeCompare(valueB) || keyA.localeCompare(keyB);
});
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
use sort from es6.
try this code:
const array =[
{ v21: 'sad sdd' },
{ v11: 'r err rr' },
{ hf32: 'erwwer fgh' },
{ z3f2: 'agfrr vbbar' },
{ t142: 'agfrr vbbar' },
{ u23: 'fa handle err' }
]
array.sort( (x,y) => {
if ( Object.values(x)[0] > Object.values(y)[0] )
return 1
else if ( Object.values(x)[0] < Object.values(y)[0] )
return -1
else {
if ( Object.keys(x)[0] > Object.keys(y)[0] )
return 1
else if ( Object.keys(x)[0] < Object.keys(y)[0] )
return -1
}
})
You can try the following code:
const array =[
{ v21: 'sad sdd' },
{ v11: 'r err rr' },
{ hf32: 'erwwer fgh' },
{ z3f2: 'agfrr vbbar' },
{ z3f1: 'agfrr vbbar' },
{ t142: 'gggoog anfa' },
{ u23: 'fa handle err' }
];
array.sort((a,b) => {
if (a[Object.keys(a)[0]] === b[Object.keys(b)[0]]) {
return Object.keys(a)[0].localeCompare(Object.keys(b)[0]);
}
return a[Object.keys(a)[0]].localeCompare(b[Object.keys(b)[0]]);
});
console.log(array);
A variant without using compare function:
const array = [
{ v21: "sad sdd" },
{ v11: "r err rr" },
{ hf32: "erwwer fgh" },
{ z3f2: "sad sdd" },
{ t142: "gggoog anfa" },
{ u23: "fa handle err" }
];
const result = array
.map(item => ({
key: Object.keys(item)[0],
value: item[Object.keys(item)[0]]
}))
.map(item => `${item.value}:${item.key}`)
.sort()
.map(item => item.split(":"))
.map(item => ({ [item[1]]: item[0] }));
console.log(result);
have to use maybe some other join char instead of ':' if it is expected in the value

Categories