JavaScript: Get non-repeating items from 2d array of objects - javascript

Compare 2d array of objects and convert it into single array of objects by getting the unique items (non-repeating in terms of id) using Javascript.
Sample input:
const data = [
[ { name: 'x', id: 1 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'y', id: 12 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'z', id: 22 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ]
];
Expected output:
const out = [
{ name: 'a', id: 1 },
{ name: 'b', id: 12 },
{ name: 'b', id: 22 }
]

Using Array#flat, convert the 2d array into one
Using Array#reduce, iterate over the latter while updating a Map where the id is the key and the count is the value
Using Map#entries, get the list of id-count pairs from the Map. Then, using Array#filter, get the ids whose count is 1. Finally, using Array#map, get the list of resulting ids
Using Array#filter, iterate over the flattened array of objects and return the elements whose ids belong to the non-repeating ids list
const data = [
[ { name: 'x', id: 1 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'y', id: 12 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ],
[ { name: 'z', id: 22 }, { name: 'a', id: 13 }, { name: 'a', id: 14 }, { name: 'a', id: 15 }, { name: 'a', id: 16 } ]
];
const arr = data.flat();
const idCountMap = arr.reduce((map, { id }) => map.set(id, (map.get(id) ?? 0) + 1), new Map);
const nonRepeatingIds =
[...idCountMap.entries()]
.filter(([ id, count ]) => count === 1)
.map(([ id ]) => id);
const nonRepeatingItems = arr.filter(({ id }) => nonRepeatingIds.includes(id));
console.log(nonRepeatingItems);

You can do this by converting the use case in two parts.
Part 1 : Concat the array of objects to a new array
var array = [];
data.forEach(val => {
array = [...array, ...val];
});
Part 2 : Find the unique values in the new array
const result = [];
const map = new Map();
for (const item of array) {
if(!map.has(item.id)){
map.set(item.id, true); // set any value to Map
result.push({
id: item.id,
name: item.name
});
}
}
console.log(result)

you have to loop the array of array and check if you already have added the id or not. In this way you can make the id unique in the result
const data= [
[{name:"x" , id:1},{name:"a" , id:13},{name:"a" , id:14},{name:"a" , id:15},{name:"a" , id:16},],
[{name:"x" , id:1},{name:"a" , id:13},{name:"a" , id:14},{name:"a" , id:15},{name:"a" , id:16},],
[{name:"x" , id:1},{name:"a" , id:13},{name:"a" , id:14},{name:"a" , id:15},{name:"a" , id:16},]
]
//loop for each array and filter
var addedId = [];
//init result
var out = [];
//loop array
data.forEach(function(arr){
//loop element in array
arr.forEach(function(obj){
// check if the id has been already added to the result
if(!addedId.includes(obj.id)){
addedId.push (obj.id);
out.push(obj);
}
})
})

Related

Filter Array of Objects with a Object reside in Nested Array property

I have the following use-case,
I have,
An array of objects that contains a list of courses
An array of objects that contains students with a nested array: studies
I need to find a courses which are not studied by any student.
How to achieve that?
follow is the code sinnpient.
let courses = [
{ id: 'A' },
{ id: 'B' },
{ id: 'C' },
{ id: 'D' }, <-- not studied by any one
{ id: 'E' },
{ id: 'F' }, <-- not studied by any one
];
let students = [
{
name: 'STD1',
study: [
{ id: 'A' },
{ id: 'C' }
]
},
{
name: 'STD2',
study: [
{ id: 'B' },
{ id: 'E' }
]
}
];
expected output
const notUsedCourse = [{ id: 'D' }, { id: 'F' }];
You can save course ids which have been studied by students into a Set so that we can check if a course has been studied later.
The advantage over the solution with filter and some combination is that this solution will be much faster when the size of courses and students gets bigger since the former has the time complexity of O(n^3) .
const courses = [
{ id: 'A' },
{ id: 'B' },
{ id: 'C' },
{ id: 'D' },
{ id: 'E' },
{ id: 'F' },
];
const students = [
{
name: 'STD1',
study: [
{ id: 'A' },
{ id: 'C' }
]
},
{
name: 'STD2',
study: [
{ id: 'B' },
{ id: 'E' }
]
}
];
const usedCourseIds = new Set(students.flatMap(student => student.study).map(course => course.id));
const notUsedCourses = courses.filter(course => !usedCourseIds.has(course.id));
console.log(notUsedCourses);
You can use .filter with .some to loop through and search if a student has the course:
let courses = [
{ id: 'A' },
{ id: 'B' },
{ id: 'C' },
{ id: 'D' },
{ id: 'E' },
{ id: 'F' },
];
let students = [
{
name: 'STD1',
study: [
{ id: 'A' },
{ id: 'C' }
]
},
{
name: 'STD2',
study: [
{ id: 'B' },
{ id: 'E' }
]
}
];
let notUsedCourse = courses.filter(
course => !students.some(
student => student.study.some(
study => study.id === course.id
)
)
);
console.log(notUsedCourse);
You could get the visited courses first and then filter all courses.
var courses = [{ id: 'A' }, { id: 'B' }, { id: 'C' }, { id: 'D' }, { id: 'E' }, { id: 'F' }],
students = [{ name: 'STD1', study: [{ id: 'A' }, { id: 'C' }] }, { name: 'STD2', study: [{ id: 'B' }, { id: 'E' }] }],
seen = students.reduce(
(seen, { study }) => study.reduce((s, { id }) => s.add(id), seen),
new Set
),
missing = courses.filter(({ id }) => !seen.has(id));
console.log(missing)

Unflatten array to Tree without parent id but with level

I'm a bit stuck with something implying recursion. I am receiving data
from an API. It looks like this:
const input = [
{ id: 'a', level: 0 },
{ id: 'b', level: 1 },
{ id: 'c', level: 1 },
{ id: 'd', level: 2 },
{ id: 'e', level: 1 },
{ id: 'f', level: 0 },
];
and I need something like
const out = [
{ id: 'a', nodes: [
{ id: 'b', nodes: [] },
{ id: 'c', nodes: [
{ id: 'd', nodes: [] },
] },
{ id: 'e', nodes: [] },
] },
{ id: 'f', nodes: [] },
];
How would you achieve that in an elegant way such as out = f(input) ?
I feel we can do a recursive nest method through a reduce but I did not manage to get it right :)
Thanks in advance!
You could use a helper array for the levels with the latest array/nodes property from the object.
const
input = [{ id: 'a', level: 0 }, { id: 'b', level: 1 }, { id: 'c', level: 1 }, { id: 'd', level: 2 }, { id: 'e', level: 1 }, { id: 'f', level: 0 }],
result = [],
levels = [result];
input.forEach(({ id, level }) =>
levels[level].push({ id, nodes: levels[level + 1] = [] })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You may try out like,
function makeObject(id){
return { id: id, nodes:[] };
}
function addObjectToNodes(array, id, node){
array.map(a => {
if(a.id === id)
a.nodes.push(node);
});
}
const nodes = [];
nodes.push(makeObject('a'));
nodes.push(makeObject('f'));
addObjectToNodes(nodes, 'a', makeObject('b'));
addObjectToNodes(nodes, 'a', makeObject('c'));
addObjectToNodes(nodes, 'a', makeObject('d'));
addObjectToNodes(nodes, 'a', makeObject('e'));
console.log(nodes);

How to groupBy an object key inside nested array of objects?

I have a nested array of objects and I want to groupBy id and form a new array. Here's my array:
mainArray = [
{ name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] },
{ name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] },
{ name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] }
]
I can flatten the entire array but it doesn't seem like the right way to do it.
My new array should look like something like this:
result = [
comapny_6: [
{
name: 'a',
age: 10,
desc: 'test1'
},
],
comapny_10: [
{
name: 'a',
age: 10,
desc: 'testa'
},
{
name: 'c',
age: 40,
desc: 'test3'
}
],
company_30 :[
{
name: 'b',
age: 20,
desc: 'test2'
},
{
name: 'c',
age: 40,
desc: 'testc'
}
]
]
I am open to suggestions on how the final data structure should look like. The bottom line is I want groupBy id so that I have information about each company separated out.
You can use reduce to loop thru the array and construct the desired object output. Use forEach to loop thru company
var mainArray = [{"name":"a","age":10,"company":[{"desc":"test1","id":6},{"desc":"testa","id":10}]},{"name":"b","age":20,"company":[{"desc":"test2","id":30}]},{"name":"c","age":40,"company":[{"desc":"test3","id":10},{"desc":"testc","id":30}]}];
var result = mainArray.reduce((c, {name,age,company}) => {
company.forEach(({id,desc}) => (c["company_" + id] = c["company_" + id] || []).push({name,age,desc}));
return c;
}, {});
console.log(result);
You can first create a 1D array using flatMap() and then use reduce() to group
const mainArray = [
{ name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] },
{ name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] },
{ name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] }
]
const flat = mainArray.flatMap(({company,...rest}) => company.map(a => ({...rest,...a})));
const res = flat.reduce((ac,{id,...rest}) => ((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac),{})
console.log(res)
Explanation
reduce() is method with returns a single value after iterating through all the array. The accumulator i.e ac in above case is set to empty object {}(which is the second argument passed to function)
In each iteration we return the updated accumulator which becomes ac for next iteration. So what we return from function is following expression
((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac)
ac[company_${id}] is using Bracket Notation which takes an expression company_${id}. It is same as
ac["company_" + id]
The above line checks if ac[company_${id}] exists in the ac then push() rest to the it.
If ac[company_${id}] is not created yet they set it to empty array [] then push() the rest to it.
The last part uses comma operator
((ac[`company_${id}`] || (ac[`company_${id}`] = [])).push(rest),ac)
The above whole expression will evaluate to the last value separated by comma , which is ac. So in each iteration we are pushing rest to the respective array and returning ac it the end. The code is equivalent to
const res = flat.reduce((ac,{id,...rest}) => {
//check if company id doesnot exist as key in ac then set it empty array
if(!ac[`company_${id}`]) ac[`company_${id}`] = [];
//push rest(which will be an object with all the keys expect id)
ac[`company_${id}`].push(rest)
//at last return ac
return ac;
})
You can achieve this with Array.reduce and inside it with an Array.forEach over the array of companies like this:
let data = [ { name: 'a',age: 10, company: [ { desc: 'test1' , id: 6 }, { desc: 'testa' , id: 10 }] }, { name: 'b',age: 20, company: [ { desc: 'test2' , id: 30 }] }, { name: 'c',age: 40, company: [ { desc: 'test3' , id: 10 }, { desc: 'testc' , id: 30 }] } ]
let result = data.reduce((r,{ name, age, company }) => {
company.forEach(({ id, desc }) =>
r[`company_${id}`] = (r[`company_${id}`] || []).concat({ name, age, desc }))
return r
}, {})
console.log(result)

Converting object format in Javascript

I have an input array of objects, which each object has the following format:
{
titleID: string,
titleName: string,
1af23_red: number,
45ua6_blue: number
}
What I know is that:
In every object, there will be always the keys titleID and titleName and then I will have several keys that have the format number_string.
The titleID and titleName values will be different among the different objects
The rest of the keys (1af23_red, 45ua6_blue, etc) will be the same in all objects and they will all have the same format 'id_name' So, if the first object has 1af23_red and 45ua6_blue as keys, all the rest will also have just those keys.
The type of array that I want back has the following format:
{
color: {
id
name
},
data: Array<
{
title: {
id
name
},
rating
}
>
}
So, example of input:
[
{
titleId: 'a',
titleName: 'atitle',
'1af_red': 50
'ba2_blue': 40
},
{
titleId: 'b',
titleName: 'btitle',
'1af_red': 30
'ba2_blue': null
},
{
titleId: 'c',
titleName: 'ctitle',
'1af_red': null
'ba2_blue': 10
}
]
I would expect back:
[
{
color: {
id: '1af',
name: 'red'
},
data: [
{
title: {
id: 'a',
name: 'atitle',
},
rating: 50
},
{
title: {
id: 'b',
name: 'btitle',
},
rating: 30
},
{
title: {
id: 'c',
name: 'ctitle',
},
rating: null
}
]
},
{
color: {
id: 'ba2',
name: 'blue'
},
data: [
{
title: {
id: 'a',
name: 'atitle',
},
rating: 40
},
{
title: {
id: 'b',
name: 'btitle',
},
rating: null
},
{
title: {
id: 'c',
name: 'ctitle',
},
rating: 10
}
]
}
]
I have tried doing this conversion with map and reduce but I am stuck. Is there an easy way to accomplish this?
Here you go.
Brief logic : Getting all the keys from the object at 0th index from data array. Looping over the keys, if key contains '_', pick the key, break it to form id and name pair, then map over all the data objects, get the score for that key and append it to the object with id and name values. Finally append this object to result array. Doing this for all the keys which contains '_'.
const data = [
{
titleId: 'a',
titleName: 'atitle',
'1af_red': 50,
'ba2_blue': 40
},
{
titleId: 'b',
titleName: 'btitle',
'1af_red': 30,
'ba2_blue': null
},
{
titleId: 'c',
titleName: 'ctitle',
'1af_red': null,
'ba2_blue': 10
}
];
const keys = Object.keys(data[0]);
const result = []
keys.map(key=> {
if(key.indexOf('_')!==-1){
const item = {}
const keyData = key.split('_')
item.color = {
id : keyData[0],
name : keyData[1]
}
item.data = []
data.map(obj => {
const newObj = {}
newObj.title = {
id : obj.titleId,
name : obj.titleName
}
newObj.rating = obj[key];
item.data.push(newObj);
});
result.push(item);
}
});
console.log(result);
You could try that
let elements = [
{
titleId: 'a',
titleName: 'atitle',
'1af_red': 50,
'ba2_blue': 40
},
{
titleId: 'b',
titleName: 'btitle',
'1af_red': 30,
'ba2_blue': null
},
{
titleId: 'c',
titleName: 'ctitle',
'1af_red': null,
'ba2_blue': 10
}
]
let colors = []
let result = []
elements.forEach(currElem => {
for(let attr in currElem){
if(attr != "titleId" && attr != "titleName"){
let color = attr.split("_")
if(!colors.some(currColor => {return currColor == color[1]})){
colors.push({
"id": color[0],
"name": color[1]
})
}
}
}
})
colors.forEach(currColor => {
result.push({
"color" : currColor,
"data": []
})
elements.forEach(currElement => {
for(let attr in currElement){
let color = []
if(attr != "titleId" && attr != "titleName"){
color = attr.split("_")
if(color[1] == currColor.name){
for(let i=0; i<result.length;i++){
if(result[i].color.name == color[1]){
result[i].data.push({
"title" : {
"id": currElement.titleId,
"name": currElement.titleName
},
"rating":currElement[attr]
})
break
}
}
}
}
}
})
})
console.log(result)

JavaScript operation array object merge

I have two arrays, and now I want to merge the two arrays.
The first array:
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
Two arrays:
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
I want to merge them into this:
var data3 = [
{name: 'aa', value: 1},
{name: 'bb', value: 2},
{name: 'cc', value: 3},
{name: 'dd', value: 0},
{name: 'ee', value: 0}
];
console.log(data3)
At present, my experience is not enough. Please help me solve this problem.
Thanks in advance.
You can try following based on following assumptions
data2 is a collection of names and expecting its length to be always more than length of data
Order of objects can be different
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
// Iterate over the names array
var data3 = data2.map(({name}) => {
// get the matched object in data corresponding to the name
var match = data.find((obj) => obj.name === name);
// if found, return value else default value to 0
return match ? match : {name, value : 0};
});
console.log(data3);
If the input arrays are indeed in order like that, then a simple .map would suffice:
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
const output = data2.map(({ name }, i) => ({ name, value: data[i] ? data[i].value : 0 }));
console.log(output);
Create an object lookup for each name using array#reduce. Extract all the values using the Object.values() from the object lookup.
const data1 = [ { name: 'aa', value: 1 }, { name: 'bb', value: 2 }, { name: 'cc', value: 3 } ],
data2 = [ { name: 'aa' }, { name: 'bb' }, { name: 'cc' }, { name: 'dd' }, { name: 'ee' } ],
result = Object.values([data1, data2].reduce((r,a) => {
a.forEach(({name, value = 0}) => {
r[name] = name in r ? r[name] : {name, value};
});
return r;
},{}));
console.log(result);
You can array#concat both the arrays and using array#reduce create an object lookup and then get all the values using the Object.values().
const data1 = [ { name: 'aa', value: 1 }, { name: 'bb', value: 2 }, { name: 'cc', value: 3 } ],
data2 = [ { name: 'aa' }, { name: 'bb' }, { name: 'cc' }, { name: 'dd' }, { name: 'ee' } ],
result = Object.values(data1.concat(data2).reduce((r,{name, value=0}) => {
r[name] = name in r ? r[name] : {name, value};
return r;
},{}));
console.log(result);
var data = [
{ name: 'aa', value: 1 },
{ name: 'bb', value: 2 },
{ name: 'cc', value: 3 }
];
var data2 = [
{ name: 'aa' },
{ name: 'bb' },
{ name: 'cc' },
{ name: 'dd' },
{ name: 'ee' }
];
let output = new Array(data2.length).fill(data2.length).map(v => new Object());
// Logic
data.forEach((val2)=> {
data2.forEach((val, i)=> {
if (val.name == val2.name){
output[i]["name"] = val.name
output[i]["value"] = val2.value
} else{
output[i]["name"] = val.name
}
})
})
output.map((val,i) => {
if (!val.hasOwnProperty("value")){
console.log(val)
val["value"] = 0
}
})
console.log("------Your Expected Format", output)

Categories

Resources