Flatten an array inside an array of objects using lodash - javascript

I have an Array like this
[
{
name: "A",
job: ["1", "2"]
},
{
name: "B",
job: ["2"]
},
{
name: "C",
job: []
}
]
How do i flatten it into something like this using lodash.
[
{
name: "A",
job: ["1"]
},
{
name: "A",
job: ["2"]
},
{
name: "B",
job: ["2"]
},
{
name: "C",
job: []
}
]
The only solution coming to my mind is to iterate recursively.
Thanks.

You don't need to use recursion if your object isn't multiple levels deep.
Instead, you can achieve this without lodash and recursion. You could use .flatMap to get each job array, and then create an individual object for each item within that job array using .map.
See example below:
const arr = [{name: "A", job: ["1", "2"]}, {name: "B", job: ["2"]}, {name: "C", job: []}];
const spreadArrObj = arr => {
return arr.flatMap(({name, job}) => {
if(job.length < 2) return {name, job}; // if the object is empty or has one one object then return the object
return job.map(elem => ({ // map each element in the job array to its own object
name,
job: [elem]
}));
});
}
console.log(spreadArrObj(arr));
See browser compatibility for .flatMap here.

let arr = [{
name: "A",
job: ["1", "2"]
},
{
name: "B",
job: ["2"]
},
{
name: "C",
job: []
}
];
let newArr = [];
for (i = 0; i < arr.length; i++) {
var job = arr[i].job
if (job.length > 0) {
for (j = 0; j < job.length; j++) {
obj = {};
obj.name = arr[i].name;
obj.job = [job[j]];
newArr.push(obj);
}
} else {
obj = {};
obj.name = arr[i].name;
obj.job = [];
newArr.push(obj);
}
}
console.log(newArr);

You can use array reduce and check length of job array. If it is more than 1 then iterate the job array and create a new object and push it to the accumulator
let data = [{
name: "A",
job: ["1", "2"]
},
{
name: "B",
job: ["2"]
},
{
name: "C",
job: []
}
]
let newData = data.reduce(function(acc, curr) {
if (curr.job.length > 1) {
curr.job.forEach(function(item) {
acc.push({
name: curr.name,
job: [item]
})
})
} else {
acc.push(curr)
}
return acc;
}, []);
console.log(newData)

Related

How can I iterate over an array(filter)?

I have this JSON structure:
const arr = [
{
id: "TaskStatuses",
rows: [
{id: "1", name: "Success"},
{id: "2", name: "Error"},
]
},
{
id: "Objects",
rows: [
{id: "1", name: "Object1"},
{id: "2", name: "Object2"},
]
},
{
id: "Groups",
rows: [
{id: "1", name: "Group1"},
{id: "2", name: "Group2"},
]
},
]
I need to create array with some condition. If my condition correctly I will push elements arr.rows.
Finally i want to get this structure:
[
{
Objects: "Object1",
Groups: "Group1"
},
{
Objects: "Object2",
Groups: "Group2"
}
]
I try to do like this
let sites = []
for (let el in arr) {
if (arr.id == "Objects") {
for (let item of el.rows) {
sites.push({Objects: item.name})
}
}
if (arr.id == "Groups") {
for (let item of el.rows) {
sites.push({Groups: item.name})
}
}
Based on comments and your mentioned expected final object array,
I modified your logic a bit to match the expected output.
My approach uses extra space, but o(nlogn) time (not sure if this is most efficient). Hope this code helps.
let firstArr = [], secondArr = [];
arr.forEach((d, i) => {
if (d.id === "Objects") {
firstArr.push(...d.rows);
}
if (d.id === "Groups") {
secondArr.push(...d.rows);
}
});
//console.log(firstArr, secondArr);
//sorting, if necessary based on id of each arr[i].rows
firstArr.sort(function (a, b) {
return a.id - b.id || a.name.localeCompare(b.name);
});
//sorting, if necessary based on id of each arr[i].rows
secondArr.sort(function (a, b) {
return a.id - b.id || a.name.localeCompare(b.name);
});
let index = 0,
size = Math.min(firstArr.length, secondArr.length);
let finalArr = [];
for (index; index < size; index++) {
if (firstArr[index].id === secondArr[index].id) {
finalArr.push({
Objects: firstArr[index].name,
Groups: secondArr[index].name,
});
}
}
//console.log(finalArr); ////this is your final Array.
Instead of building building an array, building an object would simplify things a lot. You can use the id as key for each object so you can easily find the object you want to attach properties to.
The snippet bellow loops through the different "columns". If the column matches the criteria (being either "Objects" or "Groups"), we'll loop through the rows of the column and attach the properties to the resulting object.
const arr = [{
id: "TaskStatuses",
rows: [
{id: "1", name: "Success"},
{id: "2", name: "Error"},
]
}, {
id: "Objects",
rows: [
{id: "1", name: "Object1"},
{id: "2", name: "Object2"},
]
}, {
id: "Groups",
rows: [
{id: "1", name: "Group1"},
{id: "2", name: "Group2"},
]
}];
const sites = {};
const whitelist = new Set(["Objects", "Groups"]);;
for (const column of arr) {
if (!whitelist.has(column.id)) continue;
for (const row of column.rows) {
sites[row.id] ||= {};
sites[row.id][column.id] = row.name;
}
}
console.log(sites);

How to restructure all data-items of an array by grouping them via main-key and collecting all of a group's remaining entries as grouped value-lists?

I am having a problem with merging objects in an array by key and the grouping the values that have the same key in arrays. Example:
let a = [{
key: "1",
value: "a",
value2: "b"
}, {
key: "2",
value: "a",
value2: "b"
}, {
key: "1",
value: "b",
value2: "a"
}, {
key: "1",
value: "c",
value2: "d"
}]
The results that I want to achieve is:
[{
key: "1",
value: ["a", "b", "c"],
value2: ["b", "a", "d"]
}, {
key: "2",
value: ["a"],
value2: ["b"]
}]
Does anyone have a simple solution for this in javascript/typescript?
I have read quite a few threads about similar issues, but none that are the exact same as the one I have above.
Thanks in advance!
You could try like this. It first reduces the data to a key based object and transform it to your array after.
var a = [{key:"1", value:"a",value2:"b"}, {key:"2", value:"a",value2:"b"}, {key:"1", value:"b",value2:"a"},{key:"1", value:"c",value2:"d"}]
a = a.reduce((acc, curr) => {
if (!acc[curr.key]) {
acc[curr.key] = { value: [curr.value], value2: [curr.value2] };
} else {
acc[curr.key].value.push(curr.value);
acc[curr.key].value2.push(curr.value2);
}
return acc;
}, {});
a = Object.entries(a).map(([key, value]) => ({ key, ...value }))
console.log(a);
let a = [{key:"1", value:"a",value2:"b"}, {key:"2", value:"a",value2:"b"}, {key:"1", value:"b",value2:"a"},{key:"1", value:"c",value2:"d"}]
const result = a.reduce((res, item) => {
let trans = res.find(e => e.key === item.key) || res.push(item) && item ;
Object.entries(item).map(([key, val]) => {
if(key !== "key") {
trans[key] = Array.isArray(trans[key]) ? [...trans[key], val]: [val];
}
});
return res;
}, []);
console.log('result', result);
A generic approach is implemented in a way that it just considers the main data structure of a common transformation task. It will be always agnostic about the (final) grouping key, but it does implement a configuration option for the latter.
Thus one might come up with an approach which is based on two reduce methods, one for collecting the groups by the provided group-key value and another one for collecting entry-values into (grouped) lists ...
function collectEntryValueAsListItem(accumulator, entry) {
const [key, value] = entry;
const valueList = accumulator[key] || (accumulator[key] = []);
valueList.push(value);
return accumulator;
}
function groupDataByKeyAndCollectEntryValues(collector, item) {
const { key, index, list } = collector;
const groupKey = item[key];
let groupItem = index[groupKey];
if (!groupItem) {
groupItem = index[groupKey] = { [key]: groupKey };
list.push(groupItem);
}
item = Object.assign({}, item);
delete item[key];
Object
.entries(item)
.reduce(collectEntryValueAsListItem, groupItem);
return collector;
}
const sampleList = [
{ key: "1", value: "a", value2: "b" },
{ key: "2", value: "a", value2: "b" },
{ key: "1", value: "b", value2: "a" },
{ key: "1", value: "c", value2: "d" },
];
console.log(
'... the result which the OP was looking for ...',
sampleList.reduce(groupDataByKeyAndCollectEntryValues, {
key: 'key',
index: {},
list: [],
}).list
);
console.log(
'... 2nd proof of the generic reduce approach ...',
sampleList.reduce(groupDataByKeyAndCollectEntryValues, {
key: 'value',
index: {},
list: [],
}).list
);
console.log(
'... 3rd proof of the generic reduce approach ...',
sampleList.reduce(groupDataByKeyAndCollectEntryValues, {
key: 'value2',
index: {},
list: [],
}).list
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

Get count from Array of arrays

I have an array of arrays below. With ES6, how can I get a count of each value Good, Excellent & Wow into a new array e.g [{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}] in dynamic style. I am attempting to use Object.assign but I am failing to "unique" out the count of the key plus instead, I need to use an array as I am trying to render this out on the front end. Do I need to use reduce? how?
let k = 0
const stats = {}
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
]
remarks.forEach((arr) => {
arr.map((e) => {
Object.assign(stats, { [e.name]: k = k + 1 })
})
})
console.log(stats);
Output:
stats: {Good: 8, Excellent: 11, Wow: 9}
Which is Incorrect plus I need to use an array.
Expected output:
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
Flatten the array of arrays and reduce it starting with an object like : { Good: 0, Excellent: 0, Wow: 0}
then .map the Object.entries of the result to transform it to an array :
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const result = Object.entries(
remarks.flat().reduce(
(all, { name }) => {
all[name] += 1;
return all;
},
{ Good: 0, Excellent: 0, Wow: 0 }
)
).map(([name, count]) => ({ name, count }));
console.log(result);
You can try below logic:
var data = [[{name: "Good"}],[{name: "Good"}, {name:"Excellent"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name: "Good"}, {name:"Excellent"}, {name:"Wow"}],[{name:"Excellent"}],[{name:"Excellent"}]]
var nData = [];
(data || []).forEach( e => {
(e || []).forEach(ei => {
var i = (index = nData.findIndex(d => d.name === ei.name)) >=0 ? index : nData.length;
nData[i] = {
name: ei.name,
count : (nData[i] && nData[i].count ? nData[i].count : 0)+1
}
});
});
console.log(nData);
Hope this helps!
You can use reduce, then convert the result into an array of objects:
const counts = remarks.reduce((result, list) => {
list.forEach(remark => {
result[remark.name] = (result[remark.name] || 0) + 1;
});
}, {});
const finalResult = [];
for (let name in counts) {
finalResult.push({name, count: counts[name]});
}
You could achieve this pretty easily by:
1) Flattening the nested array into 1 single level array.
2) Iterating over the flat array and create a "count map" by using Array.prototype.reduce
For example:
const remarks = [
[{
name: 'Good'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Good'
}, {
name: 'Excellent'
}, {
name: 'Wow'
}],
[{
name: 'Excellent'
}],
[{
name: 'Excellent'
}]
]
const flatten = arr => arr.reduce((accum, el) => accum.concat(el), [])
const map = flatten(remarks).reduce((accum, el) => {
if (accum[el.name]) {
accum[el.name] += 1;
} else {
accum[el.name] = 1;
}
return accum;
}, {});
console.log(map)
First find the counts using reduce than pass that to another function to get the desired view structure:
const Good = 1,
Excellent = 2,
Wow = 3;
const remarks = [
[{name: Good}],
[{name: Good}, {name:Excellent}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name: Good}, {name:Excellent}, {name:Wow}],
[{name:Excellent}],
[{name:Excellent}]
];
/*
[{name: Good, count: 4} {name: Excellent, count: 5}, {name:Wow, count:2}]
*/
function counts(remarks) {
return remarks.flat().reduce((acc, v) => {
const name = v.name;
let count = acc[name] || 0;
return {
...acc,
[name]: count + 1
}
}, {});
}
function view(counts) {
return Object.keys(counts).map(key => {
let count = counts[key];
return { name: key, count };
})
}
console.log(view(counts(remarks)));
Any time you are making a smaller set of data, or transforming data, in JavaScript reduce should be the first method you attempt to use. In this case, you may want to pair it with an indexer (hence preloading with an array of index and an array of result).
This works in one pass without needing to know the name values up front.
const remarks = [
[{name: "Good"}],
[{name: "Good"}, {name: "Excellent"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Good"}, {name: "Excellent"}, {name: "Wow"}],
[{name: "Excellent"}],
[{name: "Excellent"}]
];
const stats = remarks.reduce((p,c) => (
c.forEach( ({name}) => {
if(!p[0].hasOwnProperty(name)){
p[1].push({name:name,count:0});
p[0][name] = p[1].length - 1;
}
p[1][p[0][name]].count++;
}),p),[{},[]])[1];
console.log(stats);
A slightly more concise and definitely less readable approach (but it's worth to mention) could be:
const remarks = [
[{ name: "Good" }],
[{ name: "Good" }, { name: "Excellent" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Good" }, { name: "Excellent" }, { name: "Wow" }],
[{ name: "Excellent" }],
[{ name: "Excellent" }]
];
const stats = Object.entries(
remarks
.flat()
.reduce((acc, {name}) => (acc[name] = -~acc[name], acc), {})))
).map(([name, count]) => ({ name, count }));
console.log(stats);
It uses the comma operator in the reducer to returns the accumulator; and the bitwise operator NOT to create a counter without the needs to initialize the object upfront with all the names.
const flattenedRemarks = _.flatten(remarks);
const groupedRemarks = _.groupBy(flattenedRemarks, (remark) => remark.name);
const remarkCounts = _.mapValues(groupedRemarks, (group) => group.length);
const data = {
"mchale": {
"classes":["ESJ030", "SCI339"], // get the length
"faculty":["Hardy", "Vikrum"] // get the length
},
"lawerence":{
"classes":["ENG001"], // get the length
"faculty":["Speedman", "Lee", "Lazenhower"] // get the length
}
};
const count = Object.keys(data).map(campusName => {
const campus = data[campusName];
return Object.keys(campus).map(key => campus[key].length).reduce((p, c) => p + c, 0);
}).reduce((p, c) => p + c, 0);
console.log(count);

Remove object from array based on array of some property of that object

I have an array of objects (objList) that each has "id" property.
I have an array of strings (idsToRemove), representing IDs of the objects to remove from objList.
I find some solution but I fear it's slow, especially with the large list of objects with lots of properties.
Is there more efficient way to do this?
var idsToRemove = ["3", "1"];
var objList = [{
id: "1",
name: "aaa"
},
{
id: "2",
name: "bbb"
},
{
id: "3",
name: "ccc"
}
];
for (var i = 0, len = idsToRemove.length; i < len; i++) {
objList = objList.filter(o => o.id != idsToRemove[i]);
}
console.log(objList);
Turn the idsToRemove into a Set so that you can use Set.prototype.has (an O(1) operation), and .filter the objList just once, so that the overall complexity is O(n) (and you only iterate over the possibly-huge objList once):
var idsToRemove = ["3", "1"];
var objList = [{
id: "1",
name: "aaa"
},
{
id: "2",
name: "bbb"
},
{
id: "3",
name: "ccc"
}
];
const set = new Set(idsToRemove);
const filtered = objList.filter(({ id }) => !set.has(id));
console.log(filtered);
Note that Array.prototype.includes and Array.prototype.indexOf operations are O(N), not O(1), so if you use them instead of a Set, they may take significantly longer.
You can use Array.includes which check if the given string exists in the given array and combine it with an Array.filter.
const idsToRemove = ['3', '1'];
const objList = [{
id: '1',
name: 'aaa',
},
{
id: '2',
name: 'bbb',
},
{
id: '3',
name: 'ccc',
},
];
const filteredObjList = objList.filter(x => !idsToRemove.includes(x.id));
console.log(filteredObjList);
You don't need two nested iterators if you use a built-in lookup function
objList = objList.filter(o => idsToRemove.indexOf(o.id) < 0);
Documentation:
Array.prototype.indexOf()
Array.prototype.includes()
Simply use Array.filter()
const idsToRemove = ['3', '1'];
const objList = [{
id: '1',
name: 'aaa',
},
{
id: '2',
name: 'bbb',
},
{
id: '3',
name: 'ccc',
}
];
const res = objList.filter(value => !idsToRemove.includes(value.id));
console.log("result",res);

How to check if all objects of array are included another array?

I am trying to check if object array A includes objects from B.
let A = [
{ name: "Max" },
{ name: "Jhon" },
{ name: "Naton" },
]
let B = [
{ name: "Max" },
{ name: "Naton" },
]
So B has two objects that is in array A. How to check this ?
I am trying to achieve it with includes :
for(let entry of this.b){
if(this.a.includes(entry)){
console.log('includes');
}
}
But I get false on includes.
The method Array.includes() compare the entries of the array with the given value. Because your array entries are objects, it will not match. You have to loop at the array yourself and make the comparison.
Array.some() loops on an array and returns true if you returns true at least one. This method is useful when you want to verify something. In our example, we want to verify if the array a contains the b entry.
const a = [{
name: 'Max',
},
{
name: 'Jhon',
},
{
name: 'Naton',
},
];
const b = [{
name: 'Max',
},
{
name: 'Naton',
},
{
name: 'Daddy',
},
];
console.log(b.map(x => a.some(y => y.name === x.name)));
If I break it down :
const a = [{
name: 'Max',
},
{
name: 'Jhon',
},
{
name: 'Naton',
},
];
const b = [{
name: 'Max',
},
{
name: 'Naton',
},
{
name: 'Daddy',
},
];
// Loop on every entry of the b array
b.forEach((x) => {
// x here represent one entry
// first it will worth { name: 'Max' }, then { name: 'Naton' } ...
// for each value we are going to look at a if we can find a match
const isThereAMatch = a.some((y) => {
// y here is worth one entry of the a array
if (y.name === x.name) return true;
return false;
});
if (isThereAMatch === true) {
console.log(`We have found ${x.name} in a`);
} else {
console.log(`We have not found ${x.name} in a`);
}
});
You have to use another loop, then check the property name:
var a = [
{name: "Max"},
{name: "Jhon"},
{name: "Naton"},
];
var b = [
{name: "Max"},
{name: "Naton"},
];
for(let entry of b){
for(let entry2 of a){
if(entry2.name == entry.name){
console.log('includes', entry.name);
}
}
}
OR: You can use string version of object to check with includes():
var a = [
{name: "Max"},
{name: "Jhon"},
{name: "Naton"},
];
var b = [
{name: "Max"},
{name: "Naton"},
];
var aTemp = a.map(i => JSON.stringify(i));
var bTemp = b.map(i => JSON.stringify(i));
for(let entry of bTemp){
if(aTemp.includes(entry)){
console.log('includes', entry);
}
}
When you use Array#includes() method it will always return false because it's comparing objects which aren't equal because they aren't referencing the same object.
You should compare objects properties and not whole objects, you can do it using Array#some() method like this:
for (let entry of this.b) {
if (this.b.some(x => x.name === entry.name)) {
console.log('includes');
}
}
Demo:
A = [{
name: "Max"
},
{
name: "Jhon"
},
{
name: "Naton"
},
]
B = [{
name: "Max"
},
{
name: "Naton"
},
]
//Filter objects that exists in both arrays
let result = A.filter(el=> B.some(x => x.name === el.name));
console.log(result);

Categories