How can I sum values inside reduce with multi condition? - javascript

I want to sum up all of data base on two condition in side my reduce function.
Let's say I have data as following:
const data = [
{
"id": 1,
"option": BP,
"result": 'win',
"amount": 50
},
{
"id": 3,
"option": BP,
"result": 'win',
"amount": 20
},
{
"id": 5,
"option":VN,
"result": 'win',
"amount": 50
},
{
"id": 5,
"option":GB,
"result": 'loss',
"amount": 40
}
];
Here is my code:
data.reduce((newValueBetting, modelBetting) => {
if (
modelBetting.option === 'VN'
&& modelBetting.result === 'win'
) {
newValueBetting += modelBetting.amount;
}
return newValueBetting;
}, 0);
Regarding to this code. it will sum when my data is matches with condition. But, if I want to sum up option === 'BP' && result === 'win'. So, I don't want to write code again. Any idea? How can I make my reduce run only one time and get to this object:
{
TotalBPWin: 70,
TotalVN: 50,
TotalGBLoss: 40
}

While using reduce pass an object with all 3 key TotalBPWin, TotalVN, TotalGBLoss with initial value as 0. Then conditionally add them together.
const data = [{
id: 1,
option: "BP",
result: "win",
amount: 50,
},
{
id: 3,
option: "BP",
result: "win",
amount: 20,
},
{
id: 5,
option: "VN",
result: "win",
amount: 50,
},
{
id: 5,
option: "GB",
result: "loss",
amount: 40,
},
];
const accumulator = {
TotalBPWin: 0,
TotalVN: 0,
TotalGBLoss: 0,
};
const result = data.reduce((newValueBetting, { option, result, amount }) => {
if (option === "VN" && result === "win") {
newValueBetting["TotalVN"] += amount;
} else if (option === "BP" && result === "win") {
newValueBetting["TotalBPWin"] += amount;
} else if (option === "GB" && result === "loss") {
newValueBetting["TotalGBLoss"] += amount;
}
return newValueBetting;
}, accumulator);
console.log(result);

The idea is to use object in reduce rather than 0. The following is the simple implementation, you will probably need to refine the condition in the reduce for summation.
const data = [
{
"id": 1,
"option": 'BP',
"result": 'win',
"amount": 50
},
{
"id": 3,
"option": 'BP',
"result": 'win',
"amount": 20
},
{
"id": 5,
"option":'VN',
"result": 'win',
"amount": 50
},
{
"id": 5,
"option":'GB',
"result": 'loss',
"amount": 40
}
];
let result = {
'VN':0,
'GB':0,
'BP':0
};
data.reduce((acc,item) => {
result[item.option] += item.amount
return acc;
}, result);
console.log(result)

The following reduce will get you a sum of counts for each option. Importantly, if any option has zero wins, then there won't be an associated sum for it (See the log output).
const data = [{id:1,option:"BP",result:"win",amount:50},{id:3,option:"BP",result:"win",amount:20},{id:5,option:"VN",result:"win",amount:50},{id:5,option:"GB",result:"loss",amount:40}];
const result = data.reduce((all, el) => {
if (el.result === "win") {
all[el.option] = (all[el.option] || 0) + el.amount;
}
return all;
}, {});
console.log(result.BP);
console.log(result.VN);
console.log(result.GB);

You just need a simple loop for this and no need for if() conditionals
const data=[{id:1,option:"BP",result:"win",amount:50},{id:3,option:"BP",result:"win",amount:20},{id:5,option:"VN",result:"win",amount:50},{id:5,option:"GB",result:"loss",amount:40}];
const res= {};
data.forEach(o=>{
const k = 'Total' + o.option + o.result
res[k] = (res[k] || 0) + o.amount
});
console.log(res)

Related

JavaScript: check if duplicate key values exist in array of objects, if there are, rename it

I have an array "source"
source : [
{
"id": 1,
"secondId": 1
},
{
"id": 2,
"secondId": 1
},
{
"id": 3,
"secondId": 1
}
]
I want to rename the secondId when there are duplicate like this:
[
{
"id": 1,
"secondId": 1
},
{
"id": 2,
"secondId": 1_2
},
{
"id": 3,
"secondId": 1_3
}
]
I have this so far:
for (i = 0 ; i < source.length ; i++) {
for (j = 0 ; j < source.length ; j++){
if (source[i]["id"] != source[j]["id"] && source[i]["secondId"] === source[j]["secondId"]){
source[j]["secondId"] += "_" + (i+1);
}
console.log(source[j]["secondId"]);
}
}
and I'm getting:
[
{
"id": 1,
"secondId": 1
},
{
"id": 2,
"secondId": 1_2
},
{
"id": 3,
"secondId": 1_2_3
}
]
I tried to use some:
if(source[j]["secondId"].includes("_"+ (i+1))){
console.log(source[j]["secondId"].split("_"+ (i+1)).shift());
}
but I'm getting:
"secondId": 1
"secondId": 1
"secondId": 1_2
How can I do it? Any tips please?
A version using Array.reduce:
let source = [
{
"id": 1,
"secondId": 1
},
{
"id": 2,
"secondId": 1
},
{
"id": 3,
"secondId": 1
}];
let output = Object.values(
source.reduce((a, e, i) => {
let testId = e.secondId.toString();
if (a[testId]) {
testId = testId.split("_")[0] + "_" + (i + 1);
}
a[testId] = {...e, secondId: testId};
return a;
}, {})
);
console.log(output);
This may be a solution to achieve the (assumed) desired objective:
Code Snippet
const markDupes = arr => (
arr.reduce(
(fin, {id, secondId}) => ({
tm: {
[secondId]: (fin.tm[secondId] || 0) + 1
},
newArr: [
...fin.newArr,
{id, secondId: secondId.toString() + (
fin.tm[secondId] > 0 ? `_${fin.tm[secondId]+1}` : ''
)}
]
}),
{ tm: {}, newArr: [] }
)?.newArr
);
const source = [{
"id": 1,
"secondId": 1
},
{
"id": 2,
"secondId": 1
},
{
"id": 3,
"secondId": 1
}
];
console.log(markDupes(source));
Explanation
Use .reduce to iterate over the array of objects
Set up an initial fin object with two props tm (tracking-map) and newArr (the result-array which will have the secondId updated as desired)
For each object, destructure to directly access id and secondId
Update the map tm based on the secondId with a counter
Append to the newArr an object with id and secondId props with the latter (secondId) being converted to a string to store values of the format 1_2, 1_3, etc
NOTE: This may not be an optimal solution.
When you convert 1 to 1_2 or 1_3 it is converting a number to a string which will be a huge pain when you have a use for the number later. Instead what i have done is convert that number to a decimal for as 1.2 ,1.3 which means you can do all sorts of computation on a number without much conversion
let source = [
{
"id": 1,
"secondId": 1
},
{
"id": 2,
"secondId": 1
},
{
"id": 3,
"secondId": 1
}
];
let val = {};
for (const i in source) {
let v = source[i].secondId
val[v] = val[v] ? val[v] : v
if (val[v] !== 1) {
console.log(source[i].id, v);
source[i].secondId = Number(v.toFixed(2)) + (val[v] * 0.1)
}
val[v]++
}
console.log(source);
if you are really kean of using string instead use source[i].secondId = v+'_'+val[v] instead inside the if by changing the original source json object as well

Sort array of object on a property but keep the objects first for which property is missing

Here is an example
testWidgetOrderSort = [
{ "_id": "name", "order": 1 },
{ "_id": "is", "order": 2 },
{ "_id": "my", "order": 0 },
{ "_id": "oh I would be very first" },
{ "_id": "adam", "order": 3 }
]
Here for the the object { "_id": "oh I would be very first" } does not have the property order so it should come first.
And then the rest of the objects should be sorted according to the property "order"
So after sorting it should be,
output= [ { _id: 'oh I would be very first' },
{ _id: 'my', order: 0 },
{ _id: 'name', order: 1 },
{ _id: 'is', order: 2 },
{ _id: 'adam', order: 3 } ]
Logic is basic array sorting logic.
If both a.order and b.order are defined return 1 or -1 depending on the largest value.
If either one of them is undefined return 1 or -1 depending on the defined value.
Please Note: The value 1 and -1 determines the relative position between the two nodes. Returning 1 places a after b and -1 places a before b.
const testWidgetOrderSort = [
{ "_id": "name", "order": 1 },
{ "_id": "is", "order": 2 },
{ "_id": "my", "order": 0 },
{ "_id": "oh I would be very first" },
{ "_id": "adam", "order": 3 }
];
const output = testWidgetOrderSort.sort((a, b) => {
if( a.order !== undefined && b.order !== undefined ) {
return a.order > b.order ? 1 : -1;
} else {
return a.order !== undefined ? 1 : -1
}
});
console.log(output);
I came up with something like this:
const test = [
{ "_id": "name", "order": 1 },
{ "_id": "is", "order": 2 },
{ "_id": "my", "order": 0 },
{ "_id": "oh I would be very first" },
{ "_id": "adam", "order": 3 }
];
const x = test.sort((a, b) => {
const [STAY, SWAP] = [-1, 1];
if (!a.hasOwnProperty('order')) { return STAY; }
if (!b.hasOwnProperty('order')) { return SWAP; }
return a.order - b.order;
});
console.log(x);
You just have to pass the custom comparator function
if (!("order" in a)) return -1;
if (!("order" in b)) return 1;
else return a.order - b.order;
1) return -1 if property order doesn't exist in a.
2) return 1 if property order doesn't exist in b.
3) if both the object has order property then just sort in ascending order.
const arr = [
{ _id: "name", order: 1 },
{ _id: "is", order: 2 },
{ _id: "my", order: 0 },
{ _id: "oh I would be very first" },
{ _id: "adam", order: 3 },
];
const result = arr.sort((a, b) => {
if (!("order" in a)) return -1;
if (!("order" in b)) return 1;
else return a.order - b.order;
});
console.log(result);
If you don't care about the performance too much, the below should be fine,
const testWidgetOrderSort = [
{ "_id": "name", "order": 1 },
{ "_id": "is", "order": 2 },
{ "_id": "my", "order": 0 },
{ "_id": "oh I would be very first" },
{ "_id": "adam", "order": 3 }
];
const finalArr = testWidgetOrderSort.filter(a => typeof a.order === "undefined");
const sortedArrWithOrderItems = testWidgetOrderSort.filter(a => typeof a.order !== "undefined").sort((a,b) => (a.order > b.order ? 1 : -1));
finalArr.push(...sortedArrWithOrderItems);
console.log(finalArr);
Note: Personally I would recommend going with #Nitheesh or #decpk solution, it is more clean and performance wise better. My solution is just to give another solution for the problem

Concatenate strings within array of objects

I have the array of objects as below. I want to loop through it and get Closed property values. It should be a concatenation of all the values found in each object.
For e.g. in below case, i want final result as 121212 since it has 12 in all the 3 objects.
const data = [
{
"personId": "1208007855",
"details": {
"Closed": "12"
}
},
{
"personId": "1559363884",
"details": {
"Closed": "12"
}
},
{
"personId": "973567318",
"details": {
"Closed": "12"
}
}
]
can someone let me know how to achieve this. I tried this way but couldnt succeed in achieving the result. I could only get the value of first object. Not sure how i can concatenate and store next values in this loop
There might be a situation where some objects might not have Closed property.
const totalClosed = data.forEach(function (arrayItem) {
const x = arrayItem.details.Closed;
console.log(x);
});
Try the following:
const data = [
{
"personId": "1208007855",
"details": {
"Closed": "12"
}
},
{
"personId": "1559363884",
"details": {
"Closed": "12"
}
},
{
"personId": "973567318",
"details": {
"Closed": "12"
}
}
];
result = '';
for (let i in data) {
result += data[i].details.Closed
}
You can use the .reduce function:
data.reduce((accumulator, item) => accumulator += item.details.Closed, '')
=> 121212
Using foreach exactly the same way you were trying:
const data = [
{
personId: '1208007855',
details: {
Closed: '12'
}
},
{
personId: '1559363884',
details: {
Closed: '12'
}
},
{
personId: '973567318',
details: {
Closed: '12'
}
}
];
let totalClosed = '';
data.forEach(function (arrayItem) {
totalClosed = totalClosed + arrayItem.details.Closed;
});
console.log(totalClosed);
In functional way, using reduce
const data = [
{
"personId": "1208007855",
"details": {
"Closed": "12",
"Analyze": "10"
}
},
{
"personId": "1559363884",
"details": {
"Closed": "12",
"Analyze": "10"
}
},
{
"personId": "973567318",
"details": {
"Closed": "12",
"Analyze": "10"
}
}
]
const { Closed, Analyze } = data.reduce((acc, cur) => {
acc.Closed += cur?.details?.Closed ?? ''
acc.Analyze += cur?.details?.Analyze ?? ''
return acc
}, { Closed: "", Analyze: "" })
console.log({ Closed, Analyze })
let str = "";
for(let i=0; i<data.length;i++){
str+= data[i].details.Closed;
}
console.log(str);
Also, with forEach, the elements might not be processed in the same order (0 to n) and you may find different results than you expect.
If you want a string and have an array, the best method is reduce:
const totalClosed = data.reduce(function (accumulator, currentVal) {
const closed = currentVal.details.Closed || '';
return accumulator + closed;
}, '');
let str = ''
const totalClosed = data.forEach(function (arrayItem) {
if(arrayItem.details.Closed){
str += arrayItem.details.Closed;
}
});
console.log(str)
You can create an empty string and add to it if the closed field exists, if there is another condition in 'Closed' you can check there in the if statement.
You can reduce the data entries by destructuring the entry in the reducer and concatenating the Closed value to the running res (result). You can use the nullish-coalescing operator (??) to use an empty string instead of undefined when concatenating.
const data = [
{ "personId": "1208007855" , "details": { "Closed": "12" } },
{ "personId": "1559363884" , "details": { "Closed": "12" } },
{ "personId": "0000000000" , "details": { "Open" : "8" } }, // New!
{ "personId": "973567318" , "details": { "Closed": "12" } }
];
const value = data.reduce((res, { details: { Closed } }) => res + (Closed ?? ''), '');
console.log(value);
If you want to implement using loops Try Using:
const data = [
{
"personId": "1208007855",
"details": {
"Closed": "12"
}
},
{
"personId": "1559363884",
"details": {
"Closed": "12"
}
},
{
"personId": "973567318",
"details": {
"Closed": "12"
}
}
];
var res= ''
data.forEach((item)=>{if(item.details.Closed){ res += item.details.Closed;}})
console.log(res)
And this can also be done by using higher order functions:
Try using :
data.reduce((res, item) =>{if(item.details.Closed)
res += item.details.Closed;
return res}, '')

Get property of a grouped result from reduce in javascript

In grouped result I need the property of vendor and store in new array. but the give me some error. the error is Error: words is not defined.
how can I get vendor property from grouped list?
it is about to get result and store in new property.
const cart = {
"_id": 2,
"owner": 7,
"products": [{
"_id": {
"$oid": "5f06f9a4b8b878050fbc54b2"
},
"product": 1,
"vendor": 1,
"quantity": 2
}, {
"_id": {
"$oid": "5f06f9a4b8b878050fbc54b3"
},
"product": 2,
"vendor": 1,
"quantity": 1
}, {
"_id": {
"$oid": "5f06f9a4b8b878050fbc54b4"
},
"product": 4,
"vendor": 2,
"quantity": 1
}],
"createdAt": {
"$date": "2020-06-21T06:46:40.111Z"
},
"updatedAt": {
"$date": "2020-07-09T11:04:04.459Z"
},
"__v": 0,
"totalPrice": 265
}
const product = cart.products;
var grouped = product.reduce((dictionary, p) => {
dictionary[p.vendor] = dictionary[p.vendor] || [];
dictionary[p.vendor].push(p);
return dictionary;
}, {})
for (const p in grouped) {
console.log(grouped[p].vendor)
}
const cart = {
_id: 2,
owner: 7,
products: [
{
_id: {
$oid: "5f06f9a4b8b878050fbc54b2",
},
product: 1,
vendor: 1,
quantity: 2,
},
{
_id: {
$oid: "5f06f9a4b8b878050fbc54b3",
},
product: 2,
vendor: 1,
quantity: 1,
},
{
_id: {
$oid: "5f06f9a4b8b878050fbc54b4",
},
product: 4,
vendor: 2,
quantity: 1,
},
],
createdAt: {
$date: "2020-06-21T06:46:40.111Z",
},
updatedAt: {
$date: "2020-07-09T11:04:04.459Z",
},
__v: 0,
totalPrice: 265,
};
// const result = words.filter((word) => word.length > 6); // useless line, you do not have variable 'words'
const f = cart.products.filter((p) => p.vendor == 1);
const products = cart.products; //better variable naming
var grouped = products.reduce((dictionary, p) => {
dictionary[p.vendor] = dictionary[p.vendor] || [];
dictionary[p.vendor].push(p);
return dictionary;
}, {});
for (const p in grouped) {
console.log(grouped[p]); //is array
}
To fix this code just delete the line where you use variable words coz you didn't declare such.
To get vendor value:
grouped[p] is an array. It doesn't have a property vendor. But you can get it with:
for (const p in grouped) {
console.log(grouped[p][0].vendor);
}
or get an array of them:
let vendors = Object.getOwnPropertyNames(grouped);
Aside from the 2 lines of code which do nothing, I think you're trying to get the id of the vendor for each group - in which case this is just p in your code at the bottom which logs:
const cart = {"_id":2,"owner":7,"products":[{"_id":{"$oid":"5f06f9a4b8b878050fbc54b2"},"product":1,"vendor":1,"quantity":2},{"_id":{"$oid":"5f06f9a4b8b878050fbc54b3"},"product":2,"vendor":1,"quantity":1},{"_id":{"$oid":"5f06f9a4b8b878050fbc54b4"},"product":4,"vendor":2,"quantity":1}],"createdAt":{"$date":"2020-06-21T06:46:40.111Z"},"updatedAt":{"$date":"2020-07-09T11:04:04.459Z"},"__v":0,"totalPrice":265}
//const result = words.filter(word => word.length > 6);
//const f = cart.products.filter(p => p.vendor == 1);
const product = cart.products;
var grouped = product.reduce((dictionary, p) => {
dictionary[p.vendor] = dictionary[p.vendor] || [];
dictionary[p.vendor].push(p);
return dictionary;
}, {})
let vendor;
for (const p in grouped) {
console.log("vendor=", p, " count of items=", grouped[p].length)
}
I think this will give you the result you are looking for:
let f = cart.products.map( p => p.vendor);
let newArray = f.filter((vendor,index,arr)=>vendor!==arr[index+1]);
newArray.forEach(element => {
console.log(element);
});
You have some extraneous code in your script.
const result = words.filter(word => word.length > 6);
On line 36 you write const result = words.filter(word => word.length > 6); but words is not defined anywhere in your code and that is what generates the error.
For what concerns what you want to achieve, I am not entirely sure I understood it but, if I did, you can solve your issue like this:
const cart = {
"_id": 2,
"owner": 7,
"products": [{
"_id": {
"$oid": "5f06f9a4b8b878050fbc54b2"
},
"product": 1,
"vendor": 1,
"quantity": 2
}, {
"_id": {
"$oid": "5f06f9a4b8b878050fbc54b3"
},
"product": 2,
"vendor": 1,
"quantity": 1
}, {
"_id": {
"$oid": "5f06f9a4b8b878050fbc54b4"
},
"product": 4,
"vendor": 2,
"quantity": 1
}],
"createdAt": {
"$date": "2020-06-21T06:46:40.111Z"
},
"updatedAt": {
"$date": "2020-07-09T11:04:04.459Z"
},
"__v": 0,
"totalPrice": 265
}
const products = cart.products;
const vendors = products
.map(product => product.vendor)
.reduce((vendors, vendor) => {
if (vendors.indexOf(vendor) < 0) {
vendors.push(vendor);
}
return vendors;
}, []);
const productsByVendor = products.reduce((dictionary, p) => {
dictionary[p.vendor] = dictionary[p.vendor] || [];
dictionary[p.vendor].push(p);
return dictionary;
}, {});
console.log('Products grouped by vendor:\n', productsByVendor);
// in 'productsByVendor' the vendors are the keys of your object
console.log('Vendors:', Object.keys(productsByVendor));
/* if you want to retrieve the vendor of a specific product from 'productsByVendor'
* Assumptions:
* 1. cart.products[n].product is assumed to be a unique id (if that is not the case, you can use cart.products[n]._id instead)
* 2. I am assuming that each product can be sold by only one vendor; if a product can be sold by more than one vendor you'll have to adjust a bit the function
*/
getVendorIdFromGroupedProducts = (productId) => {
for (let key of Object.keys(productsByVendor)) {
for (let prod of productsByVendor[key]) {
if (prod.product === productId) {
return prod.vendor
}
}
}
return 'The product does not exist'
};
console.log('Vendor of product 1 is:', getVendorIdFromGroupedProducts(1));
console.log('Vendor of product 2 is:', getVendorIdFromGroupedProducts(2));
console.log('Vendor of product 3 is:', getVendorIdFromGroupedProducts(3));
console.log('Vendor of product 4 is:', getVendorIdFromGroupedProducts(4));

How to calculate average from nested values in an array using javascript?

I am trying to calculate the average duration for each stage. So in the array below - I should be able to get the average duration for 'test1', which would be 2.
jobs = [
{
"build_id": 1,
"stage_executions": [
{
"name": "test1"
"duration": 1,
},
{
"name": "test2"
"duration": 16408,
},
{
"name": "test3"
"duration": 16408,
},
]
},
{
"build_id": 2,
"stage_executions": [
{
"name": "test1"
"duration": 3,
},
{
"name": "test2"
"duration": 11408,
},
{
"name": "test3"
"duration": 2408,
},
]
}
]
My failed attempt:
avgDuration: function(jobs) {
let durationSum = 0
for (let item = 0; item < this.jobs.length; item++) {
for (let i = 0; i < this.jobs[item].stage.length; item++) {
durationSum += stage.duration
}
durationAverage = durationSum/this.jobs[item].stage.length
}
return durationAverage
What am I doing wrong? I'm not sure how to accomplish this since the duration is spread out between each job.
UPDATE:
This is return a single average for all stages rateher than per stage
<template>
<div class="stages">
<h3>
Average Duration
</h3>
<table>
<tbody>
<tr v-for="item in durations">
<td>
<b>{{ item.average}} {{ item.count }}</b>
// this returns only 1 average and 177 count instead of 10
<br />
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { calculateDuration } from "../../helpers/time.js";
import { liveDuration } from "../../helpers/time.js";
import moment from "moment";
export default {
name: "Stages",
data() {
return {
jobs: [],
durations: []
};
},
methods: {
avgDuration: function(jobs) {
var averageByName = {}; // looks like { 'name': { average: 111, count: 0 }}
for (var job of jobs) {
for(var stage of job.stage_execution) {
if (averageByName[stage.name] == null) { // we need a new object
averageByName[stage.name] = { average: 0, count: 0 };
}
// just name it so its easier to read
var averageObj = averageByName[stage.name];
// update count
averageObj.count += 1;
// Cumulative moving average
averageObj.average = averageObj.average + ( (stage.duration - averageObj.average) / averageObj.count );
console.log(averageObj.count)
}
}
return averageByName
},
},
created() {
this.JobExecEndpoint =
process.env.VUE_APP_TEST_URL +
"/api/v2/jobs/?limit=10";
fetch(this.JobExecEndpoint)
.then(response => response.json())
.then(body => {
for (let i = 0; i < body.length; i++) {
this.jobs.push({
name: body[i].job.name,
job: body[i].job,
stage_execution: body[i].stage_executions,
});
}
})
.then(() => {
this.$emit("loading", true);
})
.then(() => {
this.durations = this.avgDuration(this.jobs);
})
.catch(err => {
console.log("Error Fetching:", this.JobExecEndpoint, err);
return { failure: this.JobExecEndpoint, reason: err };
});
}
};
</script>
We can do this pretty simply and without overflow from having too many numbers by using a Cumulative moving average and a few loops.
Here is a line the relevant Wikipedia page on Moving Averages and the most relvant formula below.
I will not go into much detail with the above as there are a lot of documents describing this sort of thing. I will however say that the main reason to this over adding all the values together is that there is a far lower chance of overflow and that is why I am using it for this example.
Here is my solution with comments made in code.
var jobs = [ { "build_id": 1, "stage_executions": [ { "name": "test1", "duration": 1, }, { "name": "test2", "duration": 16408, }, { "name": "test3", "duration": 16408, }, ] }, { "build_id": 2, "stage_executions": [ { "name": "test1", "duration": 3, }, { "name": "test2", "duration": 11408, }, { "name": "test3", "duration": 2408, }, ] } ];
var averageByName = {}; // looks like { 'name': { average: 111, count: 0 }}
for (var job of jobs) {
for(var stage of job.stage_executions) {
if (averageByName[stage.name] == null) { // we need a new object
averageByName[stage.name] = { average: 0, count: 0 };
}
// just name it so its easier to read
var averageObj = averageByName[stage.name];
// update count
averageObj.count += 1;
// Cumulative moving average
averageObj.average = averageObj.average + ( (stage.duration - averageObj.average) / averageObj.count );
}
}
// print the averages
for(var name in averageByName) {
console.log(name, averageByName[name].average);
}
Let me know if you have any questions or if anything is unclear.
You could collect the values in an object for each index and map later only the averages.
var jobs = [{ build_id: 1, stage_executions: [{ name: "test1", duration: 1 }, { name: "test2", duration: 16408 }, { name: "test3", duration: 16408 }] }, { build_id: 2, stage_executions: [{ name: "test1", duration: 3 }, { name: "test2", duration: 11408 }, { name: "test3", duration: 2408 }] }],
averages = jobs
.reduce((r, { stage_executions }) => {
stage_executions.forEach(({ duration }, i) => {
r[i] = r[i] || { sum: 0, count: 0 };
r[i].sum += duration;
r[i].avg = r[i].sum / ++r[i].count;
});
return r;
}, []);
console.log(averages.map(({ avg }) => avg));
console.log(averages);
.as-console-wrapper { max-height: 100% !important; top: 0; }
I've used Array.prototype.flatMap to flatten the jobs array into an array of {name:string,duration:number} object. Also, to make more solution a bit more dynamic the function takes in a field argument which returns the average for that specific field.
const jobs = [
{
"build_id": 1,
"stage_executions": [
{
"name": "test1",
"duration": 1,
},
{
"name": "test2",
"duration": 16408,
},
{
"name": "test3",
"duration": 16408,
},
]
},
{
"build_id": 2,
"stage_executions": [
{
"name": "test1",
"duration": 3,
},
{
"name": "test2",
"duration": 11408,
},
{
"name": "test3",
"duration": 2408,
},
]
}
];
const caller = function(jobs, field) {
const filtered = jobs
.flatMap((item) => item.stage_executions)
.filter(item => {
return item.name === field;
})
const total = filtered.reduce((prev, curr) => {
return prev + curr.duration;
}, 0)
return total / filtered.length;
}
console.log(caller(jobs, 'test1'))
console.log(caller(jobs, 'test2'))
console.log(caller(jobs, 'test3'))
In case you get the error flatMap is not a function. You can add this code snippet in your polyfill or at the top of your js file.
Array.prototype.flatMap = function(lambda) {
return Array.prototype.concat.apply([], this.map(lambda));
};
PS: for demostration, I obtained the flatMap implementation from here

Categories