How to find max value in nested array nd update it? - javascript

the document is:
var house = {
"name": name1,
"steps":[
{
"step_id":1,
"value":9999,
"members": [
{
"user_id": 7,
},
{
"user_id": 1
}
]
},
{
"step_id":6,
"value":9999,
"members": [
{
"user_id": 7,
},
{
"user_id": 1
}
]
}
}
}
All I need to do is to find document by name, find there step with MAX "step_id" value and update there "value" field to -1;
I tried this but it doesnt' work for me. will be glad for any help :)
db.collection('houses').find( {"name": "name1",max: { $max : "steps.$step_id" },{$set:{"steps.$.value":-1}, function (err, doc) {
if(err){
return
}
console.log('doc updated');
} );

You could use Array#reduce and return for every loop the object with the greater step_id.
Then update the property.
var house = { name: "name1", steps:[{ step_id: 1, value: 9999, "members": [{ user_id: 7 }, { user_id: 1 }] }, { step_id: 6, value: 9999, members: [{ user_id: 7 }, { user_id: 1 }] }] };
house.steps.reduce((a, b) => a.step_id > b.step_id ? a : b).value = -1;
console.log(house);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

change data in nested array

I am trying to use an javascript algorithm to convert the data from products mode to reward mode, please help me :
var products = [{
id: "65864",name_fa:"پک دفتر 40 برگ وزيري شوميز کلاسيک 40 ",
details:[{master: 5,slave: 0,slave2: 0},{master: 11,slave: 0,slave2: 0}]
},{
id: 67532,name_fa: "100-بازی لونا",
details:[{master: 0,slave: 5,slave2: 0}]
}]
TO :
reward: [
{products: [
{
id: "65864",
name_fa:"پک دفتر 40 برگ وزيري شوميز کلاسيک 40" ,
"master": "5",
"slave": "0",
"slave2": "0"
},
{
"id": "67532",
"name_fa":"100-بازی لونا" ,
"master": "0",
"slave": "5",
"slave2": "0"
}
],
},
{"products": [
{
"id": "65864",
"name_fa":"پک دفتر 40 برگ وزيري شوميز کلاسيک 40" ,
"master": "11",
"slave": "0",
"slave2": "0"
},
{
"id": "67532",
"name_fa":"100-بازی لونا" ,
"master": "0",
"slave": "5",
"slave2": "0"
}
],
}
]
example:
[1,2],[3]
to
[1,3],[2,3]
I am trying to use an javascript algorithm to convert the data from products mode to reward mode, please help me
It's called cartesian product. I'm reusing a function and apply it on 2 arrays. The products, and the details. Then I combine those arrays (should be same length) into the required result.
// from: https://stackoverflow.com/a/36234242/3807365
function cartesianProduct(arr) {
return arr.reduce(function(a, b) {
return a.map(function(x) {
return b.map(function(y) {
return x.concat([y]);
})
}).reduce(function(a, b) {
return a.concat(b)
}, [])
}, [
[]
])
}
var products = [{
id: "65864",
name_fa: "پک دفتر 40 برگ وزيري شوميز کلاسيک 40 ",
details: [{
master: 5,
slave: 0,
slave2: 0
}, {
master: 11,
slave: 0,
slave2: 0
}]
}, {
id: 67532,
name_fa: "100-بازی لونا",
details: [{
master: 0,
slave: 5,
slave2: 0
}]
}];
var input = products.reduce(function(agg, item) {
agg.push(item.details);
return agg;
}, []);
var a = cartesianProduct(input);
// same for the id and names
var input2 = products.reduce(function(agg, item) {
var ids = []
item.details.forEach(function(_) {
ids.push({
id: item.id,
name_fa: item.name_fa
})
})
agg.push(ids);
return agg;
}, []);
var b = cartesianProduct(input2);
//console.log(JSON.stringify(a, null, 2));
//console.log(b)
var reward = [];
for (var i = 0; i < a.length; i++) {
var newGroup = {
products: []
}
for (var j = 0; j < a[i].length; j++) {
var newMan = {}
newGroup.products.push({
...b[i][j],
...a[i][j]
})
}
reward.push(newGroup)
}
console.log(reward)
.as-console-wrapper {
max-height: 100% !important
}

Group array of objects with a specific key value pushed first

Given the following Array of Objects:
[
{
"teamFK": 8650,
"code": "yellow_cards",
"typeId": 554,
"value": "5",
"side": "home"
},
{
"teamFK": 8650,
"code": "goals",
"typeId": 554,
"value": "1",
"side": "home"
},
{
"teamFK": 8990,
"code": "yellow_cards",
"typeId": 555,
"value": "2",
"side": "away"
},
{
"teamFK": 8990,
"code": "goals",
"typeId": 555,
"value": "0",
"side": "away"
}
]
I would like to group this data by code and get this result:
{
"stats": [
{
"name": "yellow_cards",
"stats": ["5","2"]
},
{
"name": "goals",
"stats": ["2","0"]
}
]
}
What I've done is the following which works but I want to make sure that the alway the stat with "side":"home" always pushed first into the array "stats": []:
const groupedStats = Object.entries(
query.reduce((acc, { typeId, value, code, side }) => {
if (!acc[code]) {
acc[code] = [];
}
acc[code].push(value);
return acc;
}, {}),
).map(([name, stats]) => ({ name, stats }));
My approach is sort it first by side using Array.sort() and then looping through the objects and adding it to stats
i created a const match to find if there is a match already so i dont have to add the name and value again basically if its not a match i'll add it to the stats array and if its a match then i'll just update the current index
const objs = [
{
teamFK: 8650,
code: "yellow_cards",
typeId: 554,
value: "5",
side: "home",
},
{
teamFK: 8650,
code: "goals",
typeId: 554,
value: "1",
side: "away",
},
{
teamFK: 8990,
code: "yellow_cards",
typeId: 555,
value: "2",
side: "away",
},
{
teamFK: 8990,
code: "goals",
typeId: 555,
value: "0",
side: "home",
},
];
let stats = [];
const transformedObj = objs
.sort((a, b) => {
if (a.side > b.side) {
return -1;
}
if (a.side < b.side) {
return 1;
}
return 0;
})
.forEach((obj) => {
const match = stats.find((stat) => stat.name === obj.code);
const statsIndex = stats.findIndex((stat) => stat.name === obj.code);
if (!match) {
stats = [...stats, { name: obj.code, value: [obj.value] }];
} else {
stats[statsIndex] = {
name: stats[statsIndex].name,
value: [...stats[statsIndex].value, obj.value],
};
}
});
console.log(stats);
You can sort array and use key grouping approach:
const data = [{"teamFK": 8650,"code": "yellow_cards","typeId": 554,"value": "5","side": "home"},{"teamFK": 8650,"code": "goals","typeId": 554,"value": "1","side": "home"},{"teamFK": 8990,"code": "yellow_cards","typeId": 555,"value": "2","side": "away"},{"teamFK": 8990,"code": "goals","typeId": 555,"value": "0","side": "away"}];
const groups = data
.sort(({ side: a }, { side: b }) => b.localeCompare(a))
.reduce((acc, { code, value }) => {
acc[code] ??= { name: code, stats: [] };
acc[code]['stats'].push(value);
return acc;
}, {});
const result = { stats: Object.values(groups) };
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }

Sorting array of Objects whilst keeping a couple of Objects in the last positions

I am having a little difficulty sorting an array of objects. At the moment I have a dataset like sorting
[
{
"index":"football",
"values":0.3,
"id":"1"
},
{
"index":"Unknown",
"values":0.5,
"id":"2"
},
{
"index":"golf",
"values":0.1,
"id":"3"
},
{
"index":"cricket",
"values":0.222,
"id":"4",
},
{
"index":"Other",
"values":0.333,
"id":"5"
},
{
"index":"netball",
"values":0.753,
"id":"6",
},
]
What I am trying to do is sort this based on their values, with the highest being on top. To do this, I done
myDataSet.sort((a, b) => parseFloat(b.values) - parseFloat(a.values));
This seems to work. However, it is the second part I am struggling with. Whatever order it sorts in I always need Other 2nd from bottom and Unknown bottom. So the above should turn into this
[
{
"index":"netball",
"values":0.753,
"id":"6",
},
{
"index":"football",
"values":0.3,
"id":"1"
},
{
"index":"cricket",
"values":0.222,
"id":"4",
},
{
"index":"golf",
"values":0.1,
"id":"3"
},
{
"index":"Other",
"values":0.333,
"id":"5"
},
{
"index":"Unknown",
"values":0.5,
"id":"2"
},
]
I tried using a filter, this seems to put them at the bottom but then the rest are no longer sorted
myDataSet.filter((e) => e.index === 'Other')
.filter((e) => e.index === 'Unknown')
.sort((a, b) => parseFloat(b.values) - parseFloat(a.values));
How can I sort based on their values but keep Other and Unknown at the bottom?
Thanks
I'd suggest creating a custom function to determine sort order, say getSortValue(), then using Array.sort():
let a = [ { "index":"football", "values":0.3, "id":"1" }, { "index":"Unknown", "values":0.5, "id":"2" }, { "index":"golf", "values":0.1, "id":"3" }, { "index":"cricket", "values":0.222, "id":"4", }, { "index":"Other", "values":0.333, "id":"5" }, { "index":"netball", "values":0.753, "id":"6", }, ]
function getSortValue(obj) {
const key = obj.index.toLowerCase();
return { "unknown": -2, "other": -1 }[key] || obj.values;
}
console.log(a.sort((a,b) => getSortValue(b) - getSortValue(a)));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could sort the array directly with an object for wanted order and a default value for all other unknown values.
Then use values directly without conversion.
const
data = [{ index: "netball", values: 0.753, id: "6" }, { index: "football", values: 0.3, id: "1" }, { index: "cricket", values: 0.222, id: "4" }, { index: "golf", values: 0.1, id: "3" }, { index: "Other", values: 0.333, id: "5" }, { index: "Unknown", values: 0.5, id: "2" }],
bottom = { Other: 1, Unknown: 2 };
data.sort((a, b) =>
(bottom[a.index] || 0) - (bottom[b.index] || 0) ||
b.values - a.values
);
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

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

Is there a "good" way to get normalised rows of data from ES nested aggregations?

Elasticsearch nested aggregations allow you to effectively group by multiple fields. But what it returns is buckets which are nested for each field you group by.
What I need is an array of objects for each group combination.
My query:
{
index : 'stats',
type : 'click',
size : 0,
body : {
aggs : {
publisher : {
terms : {
field : 'publisherData.id'
},
aggs : {
advertiser : {
terms : {
field : 'advertiserData.id'
},
aggs : {
country : {
terms : {
field : 'request.location.country.iso_code'
},
aggs : {
revenue : {
sum : {
field : 'revenueData.data.USD'
}
},
cost : {
sum : {
field : 'costData.data.USD'
}
}
}
}
}
}
}
}
}
}
}
The result, limited to one entry per field. Normally there would be more so all combinations of nested fields would have to be mapped to an array for display in a table.
{
"took": 562,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 4812178,
"max_score": 0,
"hits": []
},
"aggregations": {
"publisher": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 3114671,
"buckets": [
{
"key": 4,
"doc_count": 1697507,
"advertiser": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 555390,
"buckets": [
{
"key": 5,
"doc_count": 1142117,
"country": {
"doc_count_error_upper_bound": 13807,
"sum_other_doc_count": 544585,
"buckets": [
{
"key": "us",
"doc_count": 424137,
"revenue": {
"value": 772282
},
"cost": {
"value": 53698.84903321415
}
}
]
}
}
]
}
}
]
}
}
}
What I need (normally there would be multiple objects here, one for each combination of nested fields) :
[{
publisher:4,
advertiser:5,
country:'us',
cost:53698.84903321415,
revenue:772282
}]
What's the best way to get this result from the above nested structure or even better and if possible, from elasticsearch itself.
Any help greatly appreciated.
In plain Javascript, you could use an iterative and recursive approach - but I suggest to use some feature of ES for getting the wanted result.
function getValues(object) {
function iter(o, p) {
var add = false;
Object.keys(o).forEach(function (k) {
if (['key', 'doc_count'].indexOf(k) !== -1) {
return;
}
if (Array.isArray(o[k].buckets)) {
o[k].buckets.forEach(function (a) {
iter(a, p.concat([[k, a.key]]));
});
return;
}
add = true;
p.push([k, o[k].value]);
});
add && result.push(Object.assign({}, ...p.map(a => ({[a[0]]: a[1]}))));
}
var result = [];
iter(object.aggregations, []);
return result;
}
var data = { took: 562, timed_out: false, _shards: { total: 5, successful: 5, failed: 0 }, hits: { total: 4812178, max_score: 0, hits: [] }, aggregations: { publisher: { doc_count_error_upper_bound: 0, sum_other_doc_count: 3114671, buckets: [{ key: 4, doc_count: 1697507, advertiser: { doc_count_error_upper_bound: 0, sum_other_doc_count: 555390, buckets: [{ key: 5, doc_count: 1142117, country: { doc_count_error_upper_bound: 13807, sum_other_doc_count: 544585, buckets: [{ key: "us", doc_count: 424137, revenue: { value: 772282 }, cost: { value: 53698.84903321415 } }] } }] } }] } } };
console.log(getValues(data));

Categories