reduce array of array into a flat array of object - javascript

I'm stuck at transforming a data structure:
let d = [
{ no: 1, score: 7000 },
{ no: 2, score: 10000 },
[
{ no: 1, score: 8500 },
{ no: 2, score: 6500 }
]
];
d = d.reduce((accum, o) => {
}, [])
How can I produce this?
[{name: 'no 1', score: [7000, 8500]}, {name: 'no 2', score: [10000, 6500]}]

Here is one way to do it with simple reduce,
const result = d.flat().reduce((acc: {name: string, score: number[]}[], curr) => {
const { no, score } = curr;
let item = acc.find(a => a.name === `no ${no}`);
if (!item) {
item = { name: `no ${no}`, score: []};
acc.push(item);
}
item.score.push(score);
return acc;
}, []);
console.log(result)

let d = [{ no: 1, score: 7000 },
{ no: 2, score: 10000 },
[ { no: 1, score: 8500 },
{ no: 2, score: 6500 }]]
const result=d.flat().reduce((acc,curr)=>{
if(acc[curr.no]){
acc[curr.no].score.push(curr.score)
} else {
const keys=Object.keys(curr)
acc[curr.no]={ name: keys[0]+ ' '+curr.no, score:[curr.score]}
}
return acc;
},{})
console.log(Object.values(result))

Array.prototype.flat() call before the actual grouping, then use reduce function create the result.
let d = [{
no: 1,
score: 7000
},
{
no: 2,
score: 10000
},
[{
no: 1,
score: 8500
},
{
no: 2,
score: 6500
}
]
]
const result = d.flat().reduce((result, element) => {
const key = element.no;
if (!result[key]) {
result[key] = {
name: `no ${key}`,
score: []
}
}
result[key].score.push(element.score);
return result;
}, {})
console.log(Object.values(result))

Here's a sleek functional solution for typescript, since you included the tag-
const arr = [
{ no: 1, score: 7000 },
{ no: 2, score: 10000 },
[
{ no: 1, score: 8500 },
{ no: 2, score: 6500 },
],
];
const result = Object.entries(
arr.flat().reduce((accum: { [key: number]: number[] }, el: { no: number; score: number }) => {
accum[el.no] = (accum[el.no] ?? []).concat(el.score);
return accum;
}, {})
).map(([num, scores]) => ({ no: Number(num), scores: scores }));
console.log(result);
Result-
[
{ no: 1, scores: [ 7000, 8500 ] },
{ no: 2, scores: [ 10000, 6500 ] }
]
This flattens the inner arrays first using Array.prototype.flat. Then it uses reduce to construct an object that has the no values as keys and score values as an array of values.
In the end, the reduce results in { 1: [7000, 8500], 2: [10000, 6500] } - turn that into entries using Object.entries to get [['1', [7000, 8500]], ['2', [10000, 6500]]]
Finally, map over the entries to turn the ['1', [7000, 8500]] format into { no: 1, scores: [ 7000, 8500 ] } format and you're done!

You could take a dynamic approach which groups by the given key and takes all other properties for a new array.
const
groupBy = key => (r, value) => {
if (Array.isArray(value)) return value.reduce(group, r);
const { [key]: _, ...o } = value;
Object.entries(o).forEach(([k, v]) => ((r[_] ??= { [key]: _ })[k] ??= []).push(v));
return r;
}
data = [{ no: 1, score: 7000 }, { no: 2, score: 10000 }, [{ no: 1, score: 8500 }, { no: 2, score: 6500 }]],
group = groupBy('no'),
result = Object.values(data.reduce(group, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use Array.prototype.flat()
or if you have any array like,
let d = [
{ no: 1, score: 7000 },
{ no: 2, score: 10000 },
[
{ no: 1, score: 8500 },
{ no: 2, score: 6500 }
]
];
and then use d.flat()

Try this:
let d = [
{ no: 1, score: 7000 },
{ no: 2, score: 10000 },
[
{ no: 1, score: 8500 },
{ no: 2, score: 6500 }
]
]
const reducer = (arr, start = []) => arr.reduce((acc, next) => {
if (Array.isArray(next)) return reducer(next, acc);
for (const value of acc) {
if (value.name === `no ${next.no}`) {
value.score = [...value.score, next.score];
return acc;
}
}
return [...acc, {
name: `no ${next.no}`,
score: [next.score]
}];
}, start);
console.log(reducer(d));

In your case, you not only need to flatten the list, but also group by no property.
For flatting, you can use Array.prototype.flat(). It's quite a new feature, so if you don't use polyfills, you probably can't use it. So you can check for alternative implementations.
For grouping, you can reduce to the object where the key is no property. Note, that if multiple no properties exist, you need to save an array of all score values.
Example:
const d = [{ no: 1, score: 7000 },
{ no: 2, score: 10000 },
[ { no: 1, score: 8500 },
{ no: 2, score: 6500 }]]
const grouped = d.flat().reduce((prev, cur) => {
if (cur.no in prev) {
prev[cur.no].score.push(cur.score)
} else {
prev[cur.no] = {
name: 'no ' + cur.no,
score: [cur.score]
}
}
return prev
}, {})
console.log(Object.values(grouped))
In the example we use modifications. It is possible to do it without modifications - return a new copy during each reduction iteration. However, depending on array size, there can be performance issues. Also, it's safe to do modifications, because we create a new object in this case.

Related

Naive approach to summarize array of objects?

I faced a challenge where I needed to summarize an array of objects by the object's keys. I found a solution, but I can't shake off the feeling, that my approach is pretty naive:
const objArr = [
{ id: 1, val: "🍊" },
{ id: 1, val: "🍇" },
{ id: 1, val: "🍎" },
{ id: 2, val: "🥦" },
{ id: 2, val: "🌽" },
{ id: 2, val: "🌶" },
];
let tempArr = [];
let uniqueIdArr = [];
let sortedArr = [];
objArr.forEach((obj) => {
tempArr.push(obj.id);
uniqueIdArr = [...new Set(tempArr)];
});
uniqueIdArr.forEach((uniqueId) => {
let arr = [];
objArr.forEach((obj) => {
if (obj.id == uniqueId) {
arr.push(obj.val);
}
});
sortedArr.push({
id: uniqueId,
vals: arr,
});
});
console.log(sortedArr);
// Output: [{ id: 1, vals: [ '🍊', '🍇', '🍎' ] }, { id: 2, vals: [ '🥦', '🌽', '🌶' ] }]
Maybe there is something I don't know about JavaScript's array methods yet? Is this approach totally wrong? Is there another way, so that I could reduce the code and make it more elegant?
So many questions...
Any hint or explanation would be much appreciated. 🙈
Thanks in advance
J.
you can use Array.prototype.reduce to make your code bit shorter:
const objArr = [
{ id: 1, val: "🍊" },
{ id: 1, val: "🍇" },
{ id: 1, val: "🍎" },
{ id: 2, val: "🥦" },
{ id: 2, val: "🌽" },
{ id: 2, val: "🌶" },
];
let result = objArr.reduce((acc,e) => {
let idx = acc.findIndex(s => s.id === e.id)
if(idx > -1){
acc[idx].vals.push(e.val)
}
else{
acc.push({id:e.id,vals:[e.val]})
}
return acc
},[])
console.log(result)
Your ideas are good and explicit, but far from being optimal.
const objArr = [
{ id: 1, val: "🍊" },
{ id: 1, val: "🍇" },
{ id: 1, val: "🍎" },
{ id: 2, val: "🥦" },
{ id: 2, val: "🌽" },
{ id: 2, val: "🌶" },
];
const idValMap = new Map();
objArr.forEach(o=>{
let vals = idValMap.get(o.id);
if(!vals){
vals = [];
idValMap.set(o.id,vals);
}
vals.push(o.val);
});
console.log(Array.from(idValMap.entries()));
You can do most of it in just one loop. Take the key, check if you saw it already, if not initialize. That's it
This solution probably isn't much better but it is does use less code:
const objArr = [
{ id: 1, val: "🍊" },
{ id: 1, val: "🍇" },
{ id: 1, val: "🍎" },
{ id: 2, val: "🥦" },
{ id: 2, val: "🌽" },
{ id: 2, val: "🌶" },
];
let sortedArr = [];
objArr.forEach((item) => {
const exists = sortedArr.filter(i => i.id === item.id).length; // Check to see if we've already added an item with this ID
if (!exists) {
const matches = objArr.filter(i => i.id == item.id); // get all items with this ID
sortedArr.push({
id: item.id,
vals: matches.map(m => m.val) // We only care about the val property
});
}
});
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map and https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter for informationa bout .map() and .filter() respectively

Efficient way to get top count of an option in an array of objects

I have an array of objects like this,
[
{
user: 'A',
answers: [
{
id: 1,
score: 3,
},
{
id: 2,
score: 1,
},
...
]
},
{
user: 'B',
answers: [
...
where I have 200 users, each user answers a set of 40 questions, each question has an id and a score.
What I'm trying to do is add up each question's score. So that I can figure out which question has the highest score, which has the lowest. Aka, top question and bottom question.
What would be the best way to do this?
The current way I am doing feels a little long-winded.
const allAns = []
myList.forEach( user => allAns.push( ...user.answers ) )
const questionsScored = allAns.reduce( ( obj, cur ) => {
!obj[ cur.id ] ? obj[ cur.id ] = cur.score : obj[ cur.id ] += cur.score
return obj
}, {} )
const sortingList = []
for ( const qn in questionsScored ) {
sortingList.push( [ qn, questionsScored[ qn ] ] )
}
sortingList.sort( ( a, b ) => b[ 1 ] - a[ 1 ] )
console.log( sortingList[ 0 ], sortingList[ sortingList.length - 1 ] )
You're taking all the steps necessary so if it's working it's fine though you could replace some of your forEach() loops with available methods:
with .flatMap()
const allAns = myList.flatMap(({answers})=>answers);
and using Object.entries()
const sortingList = Object.entries(questionsScored);
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
allAns = input.flatMap(({ answers }) => answers),
questionsScored = allAns.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {}),
sortingList = Object.entries(questionsScored).sort((a, b) => b[1] - a[1]);
console.log({ max: sortingList[0], min: sortingList[sortingList.length - 1] })
Or combined into a single chained call, but it's not necessarily better.
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
sortingList = Object
.entries(
input
.flatMap(({ answers }) => answers)
.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {})
)
.sort((a, b) => b[1] - a[1]);
console.log({ max: sortingList[0], min: sortingList[sortingList.length - 1] })
If you would like to avoid the sort() call you can instead collect the low and high counts using a forEach() after the initial reduce()
const
input = [{ user: 'A', answers: [{ id: 1, score: 3, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] },],
lowScore = { count: Infinity },
highScore = { count: -Infinity };
Object
.entries(
input
.flatMap(({ answers }) => answers)
.reduce((obj, cur) => {
!obj[cur.id] ? obj[cur.id] = cur.score : obj[cur.id] += cur.score
return obj
}, {})
)
.forEach(([id, count]) => {
// update low count
if (count < lowScore.count) {
lowScore.count = count;
lowScore.id = id;
}
// update high count
if (count > highScore.count) {
highScore.count = count;
highScore.id = id;
}
});
console.log({ lowScore, highScore })
// sample data
let data = [{
user: 'A',
answers: [{
id: 1,
score: 1,
},
{
id: 2,
score: 2,
},
{
id: 3,
score: 3,
},
{
id: 4,
score: 4,
},
]
},
{
user: 'B',
answers: [{
id: 1,
score: 1,
},
{
id: 2,
score: 2,
},
{
id: 3,
score: 3,
},
{
id: 4,
score: 4,
},
]
},
]
let scoreSum = []; //scoreSum to store total score of each question
let initialValue = 0;
for (let i = 0; i < 4; i++) {
let sum = data.reduce(function (accumulator, currentValue) {
return accumulator + currentValue.answers[i].score;
}, initialValue)
scoreSum.push(sum);
}
let highestScore = Math.max(...scoreSum);
let lowestScore = Math.min(...scoreSum);
// increasing index by 1 to match with question numbers
let highestScoreIndex = scoreSum.indexOf(highestScore) + 1;
let lowestScoreIndex = scoreSum.indexOf(lowestScore) + 1;
// Array.prototype.getDuplicates returns an object where the keys are the duplicate entries
// and the values are an array with their indices.
Array.prototype.getDuplicates = function () {
var duplicates = {};
for (var i = 0; i < this.length; i++) {
if (duplicates.hasOwnProperty(this[i])) {
duplicates[this[i]].push(i);
} else if (this.lastIndexOf(this[i]) !== i) {
duplicates[this[i]] = [i];
}
}
return duplicates;
};
let sameScore = scoreSum.getDuplicates();
// checking if highest score has duplicates
// and if so then updaing highest score index
//with highest score indices
if (sameScore.hasOwnProperty(highestScore)) {
highestScoreIndex = sameScore[highestScore].map((a) => a + 1);
}
// checking if lowest score has duplicates
// and if so then updaing lowest score index
//with lowest score indices
if (sameScore.hasOwnProperty(lowestScore)) {
lowestScoreIndex = sameScore[lowestScore].map((a) => a + 1);
}
console.log(`Top question no(s): ${highestScoreIndex} highest score:${highestScore}`);
console.log(`bottom question no(s): ${lowestScoreIndex} lowest score:${lowestScore}`);
I only loop once through each answer in .answers for each user using nested reduce.
The input array got three users with each three answers.
let input = [{ user: 'A', answers: [{ id: 1, score: 2, }, { id: 2, score: 1, }, { id: 3, score: 0, }] }, { user: 'B', answers: [{ id: 1, score: 2, }, { id: 2, score: 4, }, { id: 3, score: 0, }] }, { user: 'c', answers: [{ id: 1, score: 0, }, { id: 2, score: 3, }, { id: 3, score:5, }] }]
function showBestAndWorstFrom(input) {
let highestScore = {'id': 0, 'score': -Infinity};
let lowestScore = {'id': 0, 'score': Infinity};
let currentScore = 0;
let id = 0;
const LAST_USER = input.length - 1;
let answers = input.reduce((combinedObj, user, userIndex) => {
return user.answers.reduce((_answerObj, _answer) => {
id = _answer.id
currentScore = (_answerObj[id] || 0) + _answer.score;
_answerObj[id] = currentScore;
if (userIndex == LAST_USER) {
highestScore = (highestScore.score < currentScore) ? {'id': id, 'score': currentScore } : highestScore;
lowestScore = (lowestScore.score > currentScore) ? {'id': id, 'score': currentScore } : lowestScore;
}
return _answerObj;
}, combinedObj);
}, {});
// console.log(answers); // { "1": 4, "2": 8, "3": 5 }
return {highestScore, lowestScore};
}
console.log(showBestAndWorstFrom(input))

Find objects with same prop and return the update value with its associated prop [duplicate]

I have javascript array object as below. My need is to sum value base on seach id in the array object.
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
],
For example sum of value for id 1 is 10 + 30 + 25 + 20 = 85 , It may be something link linq but I'm not sure in javascript. Thanks for all answers.
You can use a combination of filter and reduce to get the result you want:
sumOfId = (id) => array.filter(i => i.id === id).reduce((a, b) => a + b.val, 0);
Usage:
const sumOf1 = sumOfId(1); //85
Reading material:
Array.prototype.filter
Array.prototype.reduce
A way to do it with a traditional for loop
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
var sums = {};
for (var i = 0; i < array.length; i++) {
var obj = array[i];
sums[obj.id] = sums[obj.id] === undefined ? 0 : sums[obj.id];
sums[obj.id] += parseInt(obj.val);
}
console.log(sums);
running example
You can use reduce() and findIndex()
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
let res = array.reduce((ac,a) => {
let ind = ac.findIndex(x => x.id === a.id);
ind === -1 ? ac.push(a) : ac[ind].val += a.val;
return ac;
},[])
console.log(res);
JS noob here ... I guess something like this should be here too :-)
let newArray = {}
array.forEach((e) => {
!newArray[e.id] && (newArray[e.id] = 0);
newArray[e.id] += e.val;
});
You can loop on the array and check the ids.
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
var sum = 0;
var id = 1;
$.each(array, function(index, object){
if (object.id == id) {
sum += object.val;
}
});
console.log(sum);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Using Array#reduce and Map you can get the sum for each id like so. This also uses destructuring to have quicker access to properties.
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
console.log(res.get(1));
console.log(res.get(2));
If you wanted to output all the sums, then you need to use Array#from
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = Array.from(
data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
);
console.log(res);
If the format should be similar as to your original structure, you need to add a Array#map afterwards to transform it.
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = Array.from(
data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
).map(([id,sum])=>({id,sum}));
console.log(res);
You could take GroupBy from linq.js with a summing function.
var array = [{ id: 1, val: 10 }, { id: 2, val: 25 }, { id: 3, val: 20 }, { id: 1, val: 30 }, { id: 1, val: 25 }, { id: 2, val: 10 }, { id: 1, val: 20 }],
result = Enumerable
.From(array)
.GroupBy(null, null, "{ id: $.id, sum: $$.Sum('$.val') }", "$.id")
.ToArray();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/linq.js/2.2.0.2/linq.js"></script>
Here is another option, introducing an Array.prototype.sum helper:
Array.prototype.sum = function (init = 0, fn = obj => obj) {
if (typeof init === 'function') {
fn = init;
init = 0;
}
return this.reduce(
(acc, ...fnArgs) => acc + fn(...fnArgs),
init
);
};
// .sum usage examples
console.log(
// sum simple values
[1, 2, 3].sum(),
// sum simple values with initial value
[1, 2, 3].sum(10),
// sum objects
[{ a: 1 }, { a: 2 }, { a: 3 }].sum(obj => obj.a),
// sum objects with initial value
[{ a: 1 }, { a: 2 }, { a: 3 }].sum(10, obj => obj.a),
// sum custom combinations
[{ amount: 1, price: 2 }, { amount: 3, price: 4 }]
.sum(product => product.amount * product.price)
);
var array = [{ id: 1, val: 10 }, { id: 2, val: 25 }, { id: 3, val: 20 }, { id: 1, val: 30 }, { id: 1, val: 25 }, { id: 2, val: 10 }, { id: 1, val: 20 }];
// solutions
console.log(
array.filter(obj => obj.id === 1).sum(obj => obj.val),
array.filter(({id}) => id === 1).sum(({val}) => val),
array.sum(({id, val}) => id === 1 ? val : 0)
);
references:
Array.prototype.reduce
Array.prototype.filter
Arrow functions used in sum(obj => obj.val)
Object destructing assignment used in ({id}) => id === 1
Rest parameters used in (acc, ...fnArgs) => acc + fn(...fnArgs)
Conditional (ternary) operator used in id === 1 ? val : 0

How to sum value in javascript array object form specific search id?

I have javascript array object as below. My need is to sum value base on seach id in the array object.
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
],
For example sum of value for id 1 is 10 + 30 + 25 + 20 = 85 , It may be something link linq but I'm not sure in javascript. Thanks for all answers.
You can use a combination of filter and reduce to get the result you want:
sumOfId = (id) => array.filter(i => i.id === id).reduce((a, b) => a + b.val, 0);
Usage:
const sumOf1 = sumOfId(1); //85
Reading material:
Array.prototype.filter
Array.prototype.reduce
A way to do it with a traditional for loop
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
var sums = {};
for (var i = 0; i < array.length; i++) {
var obj = array[i];
sums[obj.id] = sums[obj.id] === undefined ? 0 : sums[obj.id];
sums[obj.id] += parseInt(obj.val);
}
console.log(sums);
running example
You can use reduce() and findIndex()
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
let res = array.reduce((ac,a) => {
let ind = ac.findIndex(x => x.id === a.id);
ind === -1 ? ac.push(a) : ac[ind].val += a.val;
return ac;
},[])
console.log(res);
JS noob here ... I guess something like this should be here too :-)
let newArray = {}
array.forEach((e) => {
!newArray[e.id] && (newArray[e.id] = 0);
newArray[e.id] += e.val;
});
You can loop on the array and check the ids.
var array = [
{ id: 1, val: 10 },
{ id: 2, val: 25 },
{ id: 3, val: 20 },
{ id: 1, val: 30 },
{ id: 1, val: 25 },
{ id: 2, val: 10 },
{ id: 1, val: 20 }
];
var sum = 0;
var id = 1;
$.each(array, function(index, object){
if (object.id == id) {
sum += object.val;
}
});
console.log(sum);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Using Array#reduce and Map you can get the sum for each id like so. This also uses destructuring to have quicker access to properties.
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
console.log(res.get(1));
console.log(res.get(2));
If you wanted to output all the sums, then you need to use Array#from
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = Array.from(
data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
);
console.log(res);
If the format should be similar as to your original structure, you need to add a Array#map afterwards to transform it.
const data=[{id:1,val:10},{id:2,val:25},{id:3,val:20},{id:1,val:30},{id:1,val:25},{id:2,val:10},{id:1,val:20}];
const res = Array.from(
data.reduce((a,{id,val})=>{
return a.set(id, (a.get(id)||0) + val);
}, new Map())
).map(([id,sum])=>({id,sum}));
console.log(res);
You could take GroupBy from linq.js with a summing function.
var array = [{ id: 1, val: 10 }, { id: 2, val: 25 }, { id: 3, val: 20 }, { id: 1, val: 30 }, { id: 1, val: 25 }, { id: 2, val: 10 }, { id: 1, val: 20 }],
result = Enumerable
.From(array)
.GroupBy(null, null, "{ id: $.id, sum: $$.Sum('$.val') }", "$.id")
.ToArray();
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/linq.js/2.2.0.2/linq.js"></script>
Here is another option, introducing an Array.prototype.sum helper:
Array.prototype.sum = function (init = 0, fn = obj => obj) {
if (typeof init === 'function') {
fn = init;
init = 0;
}
return this.reduce(
(acc, ...fnArgs) => acc + fn(...fnArgs),
init
);
};
// .sum usage examples
console.log(
// sum simple values
[1, 2, 3].sum(),
// sum simple values with initial value
[1, 2, 3].sum(10),
// sum objects
[{ a: 1 }, { a: 2 }, { a: 3 }].sum(obj => obj.a),
// sum objects with initial value
[{ a: 1 }, { a: 2 }, { a: 3 }].sum(10, obj => obj.a),
// sum custom combinations
[{ amount: 1, price: 2 }, { amount: 3, price: 4 }]
.sum(product => product.amount * product.price)
);
var array = [{ id: 1, val: 10 }, { id: 2, val: 25 }, { id: 3, val: 20 }, { id: 1, val: 30 }, { id: 1, val: 25 }, { id: 2, val: 10 }, { id: 1, val: 20 }];
// solutions
console.log(
array.filter(obj => obj.id === 1).sum(obj => obj.val),
array.filter(({id}) => id === 1).sum(({val}) => val),
array.sum(({id, val}) => id === 1 ? val : 0)
);
references:
Array.prototype.reduce
Array.prototype.filter
Arrow functions used in sum(obj => obj.val)
Object destructing assignment used in ({id}) => id === 1
Rest parameters used in (acc, ...fnArgs) => acc + fn(...fnArgs)
Conditional (ternary) operator used in id === 1 ? val : 0

How to combine/merge/intersect two arrays of objects in one loop?

This code works, but I feel there must be a better way without having to use Array.find() twice.
const people = [
{ id: 0, age: 99 },
{ id: 1, age: 54 },
{ id: 2, age: 54 }
];
const roles = [
{ pId: 0, responsabilites: ['make money'] },
{ pId: 1, responsabilites: ['make money', 'complain'] },
{ pId: 4, responsabilites: ['make the world a better place', 'sarcasmm'] },
];
let roomsAndOrders = people.filter(p => {
return roles.find(r => r.pId === p.id);
});
roomsAndOrders = roomsAndOrders.map(p => {
let r = roles.find(r => r.pId === p.id);
return { ...r, ...p };
});
console.log(roomsAndOrders);
Just use one .map. Your original filter doesn't make much sense - filter filters out elements from an array that you don't want, but it doesn't change the elements. You're returning objects from your filter function, and objects are truthy, so the filter doesn't actually do anything.
Edit: Or just map the other way around - map from roles to people all at once, rather than mapping from people to roles.
const people = [
{ id: 0, age: 99 },
{ id: 1, age: 54 },
{ id: 2, age: 54 }
];
const roles = [
{ pId: 0, responsabilites: ['make money'] },
{ pId: 1, responsabilites: ['make money', 'complain'] },
];
const roomsAndOrders = roles.map(role => {
const person = people.find(({ id }) => role.pId === id);
return { ...role, ...person };
});
console.log(roomsAndOrders);
To only include objects whose IDs are in both arrays, you will have to use .reduce instead, since map always returns the same number of elements as in the original array:
const people = [
{ id: 0, age: 99 },
{ id: 1, age: 54 },
{ id: 2, age: 54 }
];
const roles = [
{ pId: 0, responsabilites: ['make money'] },
{ pId: 1, responsabilites: ['make money', 'complain'] },
{ pId: 4, responsabilites: ['make the world a better place', 'sarcasmm'] },
];
const roomsAndOrders = roles.reduce((accum, role) => {
const person = people.find(({ id }) => role.pId === id);
if (person) accum.push({ ...role, ...person });
return accum;
}, []);
console.log(roomsAndOrders);
You could do this in O(n) using a hashtable:
const result = [], hash = {};
for(const person of people)
result.push(hash[person.id] = {...person});
for(const role of roles)
if(hash[role.pId])
Object.assign(hash[role.pId], role);

Categories