Nested Reduce with values in JS - javascript

Good morning, after an array.map I have an array containing the same assignments with some nested ratings:
const assignments = [
{
name: "assignmentOne",
difficultyRating: 1,
funRating: 2
},
{
name: "assignmentOne",
difficultyRating: 3,
funRating: 4
},
{
name: "assignmentOne",
difficultyRating: 5,
funRating: 1
}
]
Now I would like to get the total difficulty/fun rating, which would look like one of the following:
//Both the difficulty and fun rating in the same record
const assignmentsTotal = [
{
name: "assignmentOne",
totalDifficultyRating: 9,
totalFunRating: 7
}
]
//Difficulty and fun rating as separate records
const assignmentsDifficultyTotal = [
{
name: "assignmentOne",
totalDifficultyRating: 9
}
]
const assignmentsFunTotal = [
{
name: "assignmentOne",
totalFunRating: 7
}
]
I'm pretty confident the best way to do this is using the reduce method.
After some digging around the only thing that came close to what I want to achieve is the following article, yet I'm not able to get this to work properly. Is there a good way to do this from the starting point above, or would it be better to create separate arrays using array.map and after use the reduce method?

If you are looking for same 'name' objects in array, below should be ok:
const reducer = assignments.reduce((total, current) => {
return { name: current.name, difficultyRating : total.difficultyRating + current.difficultyRating, funRating : total.funRating + current.funRating } });
if you want to group objects by name, then look at lodash groupby function. In general, lodash is very handy in all array/obj functionalities.

const assignments = [{
name: "assignmentOne",
difficultyRating: 1,
funRating: 2
},
{
name: "assignmentOne",
difficultyRating: 3,
funRating: 4
},
{
name: "assignmentOne",
difficultyRating: 5,
funRating: 1
},
{
name: "assignmentTwo",
difficultyRating: 5,
funRating: 3
},
{
name: "assignmentTwo",
difficultyRating: 5,
funRating: 1
}
];
// if you want the totals as an array:
const assignmentsTotalArray = assignments.reduce((totalArr, item) => {
// check whether the assignment is already in the array
const assignmentIndex = totalArr.findIndex(elem => elem.name === item.name);
// if the assignment is not in the array, add it and initialize the totals
// otherwise update the totals
if (assignmentIndex === -1) {
totalArr.push({
name: item.name,
totalDifficultyRating: item.difficultyRating,
totalFunRating: item.funRating
});
} else {
totalArr[assignmentIndex].totalDifficultyRating += item.difficultyRating;
totalArr[assignmentIndex].totalFunRating += item.funRating;
}
return totalArr;
}, []);
console.log('### assignmentsTotalArray:');
console.log(assignmentsTotalArray);
// if you want the totals as an object:
const assignmentsTotalObject = assignments.reduce((totalObj, item) => {
// if the output object already contains the assignment, sum the ratings
// otherwise create a new key for the assignment and initialize the ratings
if (totalObj[item.name]) {
totalObj[item.name].totalDifficultyRating += item.difficultyRating;
totalObj[item.name].totalFunRating += item.funRating;
} else {
totalObj[item.name] = {
totalDifficultyRating: item.difficultyRating,
totalFunRating: item.funRating
};
}
return totalObj;
}, {});
console.log('### assignmentsTotalObject:')
console.log(assignmentsTotalObject);

Related

re-organizing array of objects

So I have an array of objects that contain information for different activities in different projects.
It looks something like this
const input = [
{
Project: 1,
ID: "1-1",
},
{
Project: 1,
ID: "1-2",
},
{
Project: 2,
ID: "2-1",
},
];
From this, I would like to go to this
output = [{
Project: 1,
ID1: 1 - 1,
ID2: 1 - 2,
},
{
Project: 1,
ID1: 2 - 1,
},
];
Here's what I have so far:
let currentProject = ''
let output = []
for (const e of input) {
let counter
let outputObj = {}
if (currentProject !== e.Project) {
output.push(outputObj)
counter = 1
outputObj = {}
outputObj.projectNum = e.Project
currentProject = e.Project
}
if (currentProject == e.Project) {
outputObj['ID' + counter] = e.ID
counter++
}
}
here's what I'm getting back:
output = [{
Project: 1,
ID1: 1 - 1
},
{
Project: 1,
ID1: 2 - 1
}
]
I'm not sure what the issue is, tried several times to fix it.
Could someone please help me get over the edge?
Any help will be greatly appreciated.
You can achieve this using reduce, Object.keys
const input = [{
Project: 1,
ID: "1-1",
},
{
Project: 1,
ID: "1-2",
},
{
Project: 2,
ID: "2-1",
},
];
const result = input.reduce((acc, curr) => {
const { Project, ID } = curr;
const obj = acc.find((el) => el.Project === Project);
if (obj) {
const length = Object.keys(obj).length;
obj[`ID${length}`] = ID;
} else {
acc.push({ Project, [`ID${1}`]: ID });
}
return acc;
}, []);
console.log(result);
You can try this.
const input=[{Project:1,ID:"1-1",},{Project:1,ID:"1-2",},{Project:2,ID:"2-1"}];
let temp = {};
input.map(v=>(temp[v.Project] ??= []).push(v.ID));
let output = Object.keys(temp).map(k=>{
let json = {Project:k};
temp[k].map((v,k)=>json['ID'+(Number(k+1))]=v);
return json;
});
console.log(output);
you will get the result.
[
{ Project: '1', ID1: '1-1', ID2: '1-2' },
{ Project: '2', ID1: '2-1' }
]
The way you intended to implement this assumes every project from same id comes sequentially grouped.
While #decpk answer deals with with a linear search, for performance reasons I would rather first use a dictionary and then convert to an array, AND also keep track of id quantities using a field n.
const input = [
{
Project: 1,
ID: "1-1",
},
{
Project: 1,
ID: "1-2",
},
{
Project: 2,
ID: "2-1",
},
];
const projects = {}
for (const e of input) {
let pid = e.Project
let project = projects[pid]
//First time seeing this project
if (!project) {
projects[pid] = { Project: pid, n: 1, ID1: e.ID }
}
//insert more ID
else {
project.n += 1
project[`ID${project.n}`] = e.ID
}
}
//And now converting the object to array, removing the 'n' field
const output = Object.keys(projects).map(pid => {
const obj = projects[pid]
delete obj.n
obj.Project = pid
return obj
})
You can try this way - O(n) time complexity
Using reduce to aggregate data.
Using logical nullish assignment only assigns if acc[Project] is nullish (null or undefined).
Define each additional key-value pair like:
const number = Object.keys(acc[Project]).length; // Define key format by number of existing property.
const key = `ID${number}`;
instead of using count variable.
const input=[{Project:1,ID:"1-1",},{Project:1,ID:"1-2",},{Project:2,ID:"2-1",}];
const output = input.reduce((acc, {Project, ID}) =>
{
acc[Project] ??= {Project}; // Get exist object or create new one
const number = Object.keys(acc[Project]).length; // Define key format by number of existing property.
const key = `ID${number}`;
acc[Project][key] = ID;
return acc;
}, {});
console.log(Object.values(output));
Output:
[
{
"Project": 1,
"ID1": "1-1",
"ID2": "1-2"
},
{
"Project": 2,
"ID1": "2-1"
}
]

Java script loop through array of objects

I have java script array of objects read from html table like below
depId represent the row number,
branchId represent column number and val it linked with check box (vue js v-model)
3*3 table data:
permissions= [
[{depId:1,branchId:1,val:true},{depId:1,branchId:2,val:true},{depId:1,branchId:3}],
[{depId:2,branchId:1},{depId:2,branchId:2},{depId:2,branchId:3}],
[{depId:3,branchId:1},{depId:3,branchId:2},{depId:3,branchId:3,val:true}]
]
I need to send this data to axios API, but the data should be in below format
data[0][branches][0]=1
data[0][branches][1]=2
data[0][department]=1
data[1][branches][0]=3
data[1][department]=3
I tried something like this but it have problems (the data sent in wrong indexes)
let data={};
permissions.forEach((row, i) => {
row.forEach((col, j) => {
if (col["val"] === true) {
data[`data[${i}][branches][${j}]`] = col.branchId;
data[`data[${i}][department]`] = col.deptId;
}
});
});
console.log(data);
how the loop should be to send the data in correct way?
the current result is
"data[0][branches][0]": 1,
"data[0][department]": 1,
"data[0][branches][1]": 2,
"data[2][branches][2]": 3,
"data[2][department]": 3
You forgot a couple of commas in your permissions object.
The next problem is that you were trying to check for departmentId in permissions, but it's actually depId there.
The next thing is that you do not need to define and track i and j, they are conveniently provided to you in a forEach function as the second argument that is passed to the running function.
Here is a working version of what you were trying to achieve:
permissions = [
[{
depId: 1,
branchId: 1,
val: true
}, {
depId: 1,
branchId: 2,
val: true
}, {
depId: 1,
branchId: 3
}],
[{
depId: 2,
branchId: 1
}, {
depId: 2,
branchId: 2
}, {
depId: 2,
branchId: 3
}],
[{
depId: 3,
branchId: 1
}, {
depId: 3,
branchId: 2
}, {
depId: 3,
branchId: 3,
val: true
}]
]
let data = {};
let j = 0;
permissions.forEach((row) => {
let i = 0;
let departmentSeen = false;
row.forEach((col) => {
if (col["val"] === true) {
data[`data[${j}][branches][${i}]`] = col.branchId;
data[`data[${j}][department]`] = col.depId;
i++;
departmentSeen = true;
}
});
if (departmentSeen) {
j++;
}
});
console.log(data);

Javascript modified array before group by

i am trying to modified array before groupBy
i have this array
[
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
]
i am trying to modified as per count value has in object value count 3 + 2 = 5 object should in array
witch function i use ? for output array like this
[
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
{
name: 'BoxTwo',
count: 2
},
]
It looks like you want to build a new array based on the contents of an existing one. For this, I would turn to Array.reduce(). For example:
const input = [
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
];
const output = input.reduce((boxes, box) => {
const { count = 0 } = box;
for (let i = 0; i < count; i++) {
boxes.push(box);
}
return boxes;
}, []);
You can iterate and flatten the before array with Array.flatMap(), and generate an array of items according to count using Array.from().
Note: You need to shallow clone the objects, if you don't want multiple references to the same object. This prevent all connected "objects" from changing, when one of them is changed.
const before = [{"name":"BoxOne","count":3},{"name":"BoxTwo","count":2}]
const after = before.flatMap(o =>
Array.from({ length: o.count }, () => ({ ...o }))
)
console.log(after)
let newArr = yourArr.reduce((arr, item) => {
for (let i = 0; i < item.count; i++) {
arr.push(item);
}
return arr;
}, []);
console.log(newArr);
you can use reduce array's function. And make iterating by count inside it
or if you need create new objects try spread syntax
let newArr = yourArr.reduce((arr, item) => {
for (let i = 0; i < item.count; i++) {
arr.push({...item});
}
return arr;
}, []);
console.log(newArr);
If your intention is to clone the object in the array the number of times the count inside it, here is the solution.
let boxes = [
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
]
let newBoxes = [];
boxes.forEach(box =>
{
let count = box.count;
while(count > 0)
{
newBoxes.push(box);
count--;
}
}
);
Your newBoxes will have the expected output.
[
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxOne',
count: 3
},
{
name: 'BoxTwo',
count: 2
},
{
name: 'BoxTwo',
count: 2
},
]
Here's a more concise way to use .reduce().
const boxes = [{"name":"BoxOne","count":3},{"name":"BoxTwo","count":2}]
const newBoxes = boxes.reduce((acc, o) =>
[...acc, ...new Array(o.count).fill(o)]
, [])
console.log(JSON.stringify(newBoxes, null, 2))
It assumes you don't mind using the same object reference.
It uses spread syntax to always fill the return value with the current content of the accumulator, and the content of a new array containing the desired number of objects for the current iteration, using .fill().

Merge an item attribute of one array into items of another array

I have a few questions in regards to what would be the best approach to do the following:
Call two different API:
axios.get(contents);
axios.get(favorites);
Response will Look like this:
contents: [
{
id: 1,
value: someValue
},
{
id: 2,
value: someValue
}
];
favorites: [
{
id: 1,
contentId: 2
}
];
What would be the best approach to loop through each favorite and add an element to the contens array such as isFavorite: true when the contentId matches the id. It should look as follows:
contents: [
{
id: 1,
value: someValue
{,
{
id: 2,
value: someValue
isFavorite: true
{
];
What would be the best place to do this and is there any ES6 syntax that can easily do this? I currently have the two actions separate, one that gets the contents and one that gets the favorites, I could possibly merge those or combine them at the reducer.
Any suggestions?
You can use a Set to collect all contentId values from favorites and then iterate through your contents array. This has better time complexity than using some on an array because calling .has() on a Set is O(1):
let contents = [{
id: 1,
value: 'someValue1'
},
{
id: 2,
value: 'someValue2'
},
{
id: 3,
value: 'someValue'
}
];
let favorites = [{
id: 1,
contentId: 2
},
{
id: 2,
contentId: 3
}
];
let favoriteContents = new Set(favorites.map(f => f.contentId));
contents.forEach(c => {
if (favoriteContents.has(c.id)) c.isFavorite = true;
});
console.log(contents);
const newContents = contents.map((content) => {
const foundFavorite = favorites.find((favorite) => favorite.contentId === content.id)
if (foundFavorite) {
return {
...content,
isFavorite: true,
}
}
return content
});
You firstly need to have the promises from your API calls, and when both of them are complete you can then carry out the merge of the results.
const contentsApi = () => Promise.resolve([
{
id: 1,
value: 'foo'
},
{
id: 2,
value: 'bar'
}
])
const favouritesApi = () => Promise.resolve([
{
id: 1,
contentId: 2
}
])
let contents;
let favourites;
const contentsApiCall = contentsApi().then(res => {
contents = res;
})
const favouritesApiCall = favouritesApi().then(res => {
favourites = res;
})
Promise.all([contentsApiCall, favouritesApiCall]).then(() => {
const merged = contents.map(content => {
if(favourites.some(favourite => favourite.contentId === content.id)){
return {
...content,
isFavourite: true
}
} else {
return content;
}
})
console.log(merged)
// do whatever you need to do with your result, either return it if you want to chain promises, or set it in a variable, etc.
})

Create object with new key and assign same array

Do you have an optimised solution for the following.
let say,
x = { users: [ {id: 1}, {id:2}, {id:3} ] }
I want to make a new key with same values, the output should be,
{ users: { list: [ {id: 1}, {id:2}, {id:3} ], count: 3 }
Using only JS or Underscore with one line code w/o any extra effort.
I know other tricks to do the same, but do we have one line solution for the same ?
Help appreciated...
Thanks.
Create the object, and assign the x array to list. However, count should be a getter, since the list length might change:
const x = { users: [{ id: 1 }, { id: 2 }, { id: 3 }] };
const result = {
users: {
list: x.users,
get count() { return this.list.length; }
}
};
console.log(JSON.stringify(result));
result.users.list.push({ id: 4 });
console.log(JSON.stringify(result));
I'm not sure why this is to be an optimised solution because this is a simple plain-JavaScript problem:
let x = { users: [{ id: 1 }, { id: 2 }, { id: 3 }] };
let result = {
users: {
list: x.users,
count: x.users.length
}
};
console.log(result);
Sure, just define the property as an object
const obj = {
users: [{
id: 1
}, {
id: 2
}, {
id: 3
}]
};
obj.users = { list: obj.users, count: obj.users.length };
console.log(obj);
I recommend focusing on code clarity rather than on line conservation though

Categories