I have collected an array of weather data that looks like this:
const data = [{
"city_name": "London",
"lat": 51.507351,
"lon": -0.127758,
"main": {
"temp": 289.89,
"temp_min": 288.468,
"temp_max": 291.15,
"feels_like": 287.15,
"pressure": 1004,
"humidity": 77
},
"wind": { "speed": 5.1, "deg": 230 },
"clouds": { "all": 90 },
"weather": [
{
"id": 804,
"main": "Clouds",
"description": "overcast clouds",
"icon": "04n"
}
],
"dt": 1593561600,
"dt_iso": "2020-07-01 00:00:00 +0000 UTC",
"timezone": 3600
},
...
];
This data continues in ascending date order (hour by hour), for the last 40 years.
(sample: https://pastebin.com/ciHJGhnq ) - the entire dataset is over 140MB.
From this data, I'd like to obtain the average temperature (object.main.temp) for each Month and Week of month, across the entire dataset.
The question I am trying to answer with my data is:
What is the average temperature for January, across the last 40 years.
What is the average temperature for February, across the last 40 years.
...
(get the temperature of each week in January and divide by the number of weeks, repeat for all of the other Januaries in the dataset and average that out too).
Repeat for remaining months.
The output I am aiming to create after parsing the data is:
{
[
"JANUARY": {
"weekNumber": {
"avgWeekTemp": 100.00
}
"avgMonthTemp": 69.00,
...
},
...
]
}
The city name & structure of the objects are always the same, in this case London.
// build a unique number of months
// work through our data to work out the week numbers
// work through the data once again and place the data in the right week inside finalOutput
// work through the final output to determine the average values
Unfortunately I'm not very proficient in JavaScript, so I couldn't get past the second obstacle:
"use strict";
const moment = require("moment");
const data = require("./data.json");
let months = [
{
January: [],
},
{
February: [],
},
{
March: [],
},
{
April: [],
},
{
May: [],
},
{
June: [],
},
{ July: [] },
{ August: [] },
{ September: [] },
{ October: [] },
{ November: [] },
{ December: [] },
];
const finalOutput = [];
finalOutput.push(...months);
data.forEach((object) =>
finalOutput.forEach((month) => {
if (
Object.keys(month)[0] === moment(new Date(object.dt_iso)).format("MMMM")
) {
[month].push(object.dt_iso);
}
})
);
console.log(finalOutput);
Which only returned the array of months with nothing in each month.
[
{ January: [] },
{ February: [] },
{ March: [] },
{ April: [] },
{ May: [] },
{ June: [] },
{ July: [] },
{ August: [] },
{ September: [] },
{ October: [] },
{ November: [] },
{ December: [] }
]
How can I calculate the average values per week & month across my entire data set?
I'm going to write your script for you, but while you wait here's some high-level guidance.
First, let's study your data. Each row is an hourly weather measurement. As a result, each datapoint you want will be an aggregate over a set of these rows. We should organize the script along those lines:
We'll write a function that accepts a bunch of rows and returns the arithmetic mean of the temperatures of those rows: function getAvgTemp(rows) => Number
We'll write another function that takes a bunch of rows, plus the desired month, and returns all the rows for just that month: function getRowsByMonth(month) => Array(rows)
We'll write another function that takes a bunch of rows, plus the desired week number, and returns all the rows for just that week: function getRowsByWeekNumber(rows, weekNumber) => Array(rows)
^^ that's if "week number" means 1-52. But if "week number" means "week within the month," then instead we'll do:
A function will also take a month: function getRowsByMonthWeek(rows, month, weekNumber) => Array(rows)
From these basic building blocks, we can write a routine that assembles the data you want.
What would that routine look like?
Probably something like this:
Loop through all the months of the year. We won't look in the data for these months, we'll hard-code them.
For each month, we'll call getRowsByMonth on the full data set. Call this set monthRows.
We'll pass monthRows to getAvgTemp -- it doesn't care what the timespan is, it just extracts and crunches the temp data it receives. That's our avgMonthTemp solved for.
Depending on what you mean by "week number," we'll divide the monthRows into smaller sets and then pass each set into getAvgTemp. (The hardest part of your script will be this division logic, but that's not to say it will be that hard.) This gives us your weekly averages.
We'll assemble these values into a data structure and insert it into the final structure that ultimately gets returned/logged.
Here's the implementation. It's a little different than I expected.
The biggest change is that I did some pre-processing up front so that the date values don't have to be parsed multiple times. While doing that, I also calculate each row's weekNumber. As a consequence, the week logic took the form of grouping rows by their weekNumbers rather than querying the dataset by weekNumber.
Some notes:
I decided that "weekNumber" means "week-of-year."
Instead of using Moment, I found a week-number algorithm on StackOverflow. If you want to use Moment's algo instead, go ahead.
The output data structure is not what you described.
Your example is not valid JSON, so I made a guess as to what you had in mind.
Here's an example of what it looks like:
{
"JUNE": {
"avgMonthTemp": 289.9727083333334,
"avgWeekTemps": {
"25": 289.99106382978727,
"26": 289.11
}
},
"JULY": {
"avgMonthTemp": 289.9727083333334,
"avgWeekTemps": {
"27": 289.99106382978727,
"30": 289.11
}
}
}
The output will include a top-level entry for every month, whether or not there is any data for that month. However, the avgWeekTemps hash will only have entries for weeks that are present in the data. Both behaviors can be changed, of course.
It's a reusable script that processes arbitrary JSON files in the format you shared.
You mentioned that each file has data from one city, so I figured you'll be running this on multiple files. I set it up so you can pass the path to the data file as a command-line argument. Note that the CLI logic is not sophisticated, so if you're doing funky things you will have a bad time. Doing CLI stuff well is a whole separate topic.
If your data for London is in a file named london.json, this is how you would process that file and save the results to the file london-temps.json:
$ node meantemp.js london.json > london-temps.json
// meantemp.js
const FS = require('fs')
// sets the language used for month names
// for language choices, see: http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry
const MONTH_NAME_LANG_CODE = 'en-US'
// generate the list of month names once
const MONTH_NAMES = Array(12).fill().map(
( _, monthNum ) => new Date(2020, monthNum).toLocaleDateString(MONTH_NAME_LANG_CODE, { month: 'long' }).toUpperCase()
)
main()
function main() {
let filepath = process.argv[2]
let cityData = readJsonFile(filepath)
// before working on the data, prep the date values for processing
let allRows = cityData.map(row => {
let _date = new Date(row.dt_iso)
let _weekNum = getWeekNum(_date)
return { ...row, _date, _weekNum }
})
let output = MONTH_NAMES.reduce(( hash, monthName, monthNum ) => {
// grab this month's rows
let monthRows = getRowsForMonth(allRows, monthNum)
// calculate monthly average
let avgMonthTemp = getMeanTemp(monthRows)
// calculate weekly averages
let rowsByWeekNum = groupRowsByWeekNum(monthRows)
let avgWeekTemps = Object.keys(rowsByWeekNum)
.reduce(( hash, weekNum ) => ({
...hash,
[weekNum]: getMeanTemp(rowsByWeekNum[weekNum])
}), {})
return {
...hash,
[monthName]: { avgMonthTemp, avgWeekTemps }
}
}, {})
console.log(JSON.stringify(output))
}
function readJsonFile( path ) {
try {
let text = FS.readFileSync(path, 'utf8')
return JSON.parse(text)
} catch ( error ) {
if(error.code === 'ENOENT') {
console.error(`Could not find or read path ${JSON.stringify(path)}`)
process.exit()
} else if(error instanceof SyntaxError) {
console.error(`File is not valid JSON`)
process.exit()
} else {
throw error
}
}
}
function getRowsForMonth( rows, monthNum ) {
return rows.filter(row => monthNum === row._date.getUTCMonth())
}
function groupRowsByWeekNum( rows ) {
return rows.reduce(( hash, row ) => {
if(!hash.hasOwnProperty(row._weekNum)) {
hash[row._weekNum] = []
}
hash[row._weekNum].push(row)
return hash
}, {})
}
// ISO8601-compliant week-of-year function
// taken from https://stackoverflow.com/a/39502645/814463
// modified by me to prevent mutation of args
function getWeekNum( date ) {
// if date is a valid date, create a copy of it to prevent mutation
date = date instanceof Date
? new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
: new Date()
let nDay = (date.getDay() + 6) % 7
date.setDate(date.getDate() - nDay + 3)
let n1stThursday = date.valueOf()
date.setMonth(0, 1)
if (date.getDay() !== 4) {
date.setMonth(0, 1 + ((4 - date.getDay()) + 7) % 7)
}
return 1 + Math.ceil((n1stThursday - date) / 604800000)
}
function getMeanTemp( hourlyReadings ) {
let temps = hourlyReadings.map(reading => reading.main.temp)
let mean = getMean(temps)
return mean
}
function getMean( numbers ) {
let sum = numbers.reduce(( sum, num ) => sum + num, 0)
let mean = sum / numbers.length
return mean
}
Related
I have an array:
const arr1 = [
"Strongly positive",
"Positive",
"Neutral",
"Negative",
"Strongly negative"
];
and responses object:
const responses = [
{ values: { QID16: 3 } },
{ values: { QID16: ["1", "2", "3"] } },
{ values: { QID16: 1 } }
];
The goal is to create a function that returns a map.
The map's keys are the elements of arr1 and the values are the count of those elements if their index + 1 appears in the responses object.
For example, responses[0].values.QID16 is 3 which is Neutral. (Neutral has index 2)
The problem is when the values in responses is an array like in responses[1]
I created the following function:
function getCounts(mainId, choices, responses) {
let choicesCounts = new Map();
choices.forEach((choice, i) => {
choicesCounts.set(choice, 0);
const id = i + 1;
responses.forEach((response) => {
if (response.values[mainId] && response.values[mainId] === id) {
choicesCounts.set(choice, choicesCounts.get(choice) + 1);
}
if (
response.values[mainId] &&
Array.isArray(response.values[mainId]) &&
response.values[mainId].includes(id.toString())
) {
// this is the part where I need help
response.values[mainId].forEach((n) => {
choicesCounts.set(
choices.at(parseInt(n, 10) - 1),
choicesCounts.get(choices.at(parseInt(n, 10) - 1)) + 1
);
});
}
});
});
return choicesCounts;
}
It would be called like this:
console.log(getCounts("QID16", arr1, responses));
Desired output:
// desired output is a map not an object
const desiredOutput = {
"Strongly positive": 2, // number 1 appears twice in responses
Positive: 1, // number 2 appers once in responses
Neutral: 2,
Negative: 0,
"Strongly negative": 0
};
It works in the case where the values are numbers but not when they're arrays.
What is wrong with this function? Any suggestions to make it simpler?
I think your problem lies in the way how you approached to solve this problem.
Currently you are iterating over your responses array once for each possible value from arr1.
When you encounter the array of values within a responses entry, you check whether it contains a value you are currently "looking for", and then increment ALL choices that were made. Since this will get looped over several times, you will also increment all of them several times and get the wrong counts.
Without giving you the exact code for changing this, just restructuring your function this way should already help coming to a cleaner solution:
Change the processing order. Instead of searching for individual values in your responses, try to do a "pre-processing" step where you create a map which represents the count of each unique key you encounter in the responses values.
The result will be almost what you were looking for, only the keys are not mapped to the values of arr1 yet. This has the advantage of only requiring a single iteration over your responses array, getting rid of the problem of counting some values multiple times.
In case you chose this approach because the responses values might contain values you are not interested in and you want to "skip" those, that behaviour can be replicated in the pre-processing phase by checking if the currently inspected value is contained in the arr1 array before writing it to the result map.
Does this cover the problem you were seeing, or was there an actual other error you encountered?
The OP should think about choosing an approach which breaks the entire task apart into smaller ones.
From the ratings-array (the one which literally names the ratings) create a rating-value (the ones that come with the response item's values) based map for looking up rating-keys (the rating-names which are the keys to the to be achieved rating-counts map).
const ratingValuesToKeys = new Map([
"Strongly positive",
"Positive",
"Neutral",
"Negative",
"Strongly negative",
].map((key, idx) => [idx + 1, key]));
console.log(Object.fromEntries([...ratingValuesToKeys]));
Also from the very same ratings-array create a rating-name based map for counting/summing up the occurrences of related rating-values.
const ratingCounts = new Map([
"Strongly positive",
"Positive",
"Neutral",
"Negative",
"Strongly negative",
].map(key => [key, 0]));
console.log(Object.fromEntries([...ratingCounts]));
Sanitize and collect all key specific rating-values for they are occurring as different types like number and/or string values as well as arrays.
const ratingValues = [
{ values: { QID16: 3 } },
{ values: { QID16: ["1", "2", "3"] } },
{ values: { QID16: 1 } },
]
.reduce((result, { values }) => {
if (values.hasOwnProperty('QID16')) {
const rating = values['QID16'];
if (Array.isArray(rating)) {
result
.push(
...rating
.map(value =>
parseInt(value, 10)
)
);
} else {
result
.push(parseInt(rating, 10));
}
}
return result;
}, []);
console.log({ ratingValues });
Based on all sanitized key specific rating-values do update each rating's occurrence by incrementing the related rating-key's count-value.
The final combined implementation and example code then might look similar to this ...
const ratings = [
"Strongly positive",
"Positive",
"Neutral",
"Negative",
"Strongly negative",
];
const responses = [
{ values: { QID16: 3 } },
{ values: { QID16: ["1", "2", "3"] } },
{ values: { QID16: 1 } },
];
function getRatingCounts(responseValueKey, ratings, responses) {
// create a rating-value based map for looking up rating-keys.
const ratingValuesToKeys = new Map(
ratings
.map((key, idx) => [idx + 1, key])
);
// create a rating-key based map for counting/summing up ratings.
const ratingCounts = new Map(
ratings
.map(key => [key, 0])
);
// sanitize and collect all key specific rating-values
// for they are occurring as different types ... like
// number and/or string values as well as arrays.
const ratingValues = responses
.reduce((result, { values }) => {
if (values.hasOwnProperty(responseValueKey)) {
const rating = values[responseValueKey];
if (Array.isArray(rating)) {
result
.push(
...rating
.map(value =>
parseInt(value, 10)
)
);
} else {
result
.push(parseInt(rating, 10));
}
}
return result;
}, []);
// based on all sanitized key specific rating-values
// do update each rating's occurrence by incrementing
// the related rating-key's count-value.
ratingValues
.forEach(value => {
const ratingKey = ratingValuesToKeys.get(value);
const ratingCount = ratingCounts.get(ratingKey);
ratingCounts.set(ratingKey, ratingCount + 1);
});
return ratingCounts;
}
const ratingCounts = getRatingCounts('QID16', ratings, responses);
console.log({
ratingCounts,
'counts as entry list': [...ratingCounts],
'counts as object': Object.fromEntries([...ratingCounts]),
});
.as-console-wrapper { min-height: 100%!important; top: 0; }
This is a calendar check. For example: 100 days of history is available but 101 days back would be disabled for certain market.
Code is following:
const todaysDate3 = dayjs().subtract(101, 'days').format('DD')
const todaysDate4 = dayjs().subtract(100, 'days').format('DD') //etc
cy.visit(`http://calendar.whatever/ICN&markettype=ICN`);
cy.get('.calendar-table').click
cy.get('.calendar-table').contains('td',(todaysDate3)).should("have.class","disabled")
cy.get('.calendar-table').contains('td',(todaysDate4)).should("have.class","enabled")
What would be the best practice to make such test for 80, 100, 365 etc days as every market. Worst case scenario I can think of is something like
export const 100days = [{
"url": (`http://calendar.whatever/ICN`),
"has100days": true
}]
and like this for every possible value and using
if (curr.has100days) //do something } else if (curr.has365days){do something else}
Probably best would be to write some kind of function?
thank you for your help!
Since you visit each market, the data-driven approach you indicate is best.
const history = [
{ market: 'ICN', days: 100 },
{ market: 'ZYX', days: 120 },
...
]
history.forEach(data => {
cy.log(`Testing ${data.market} with ${data.days} history`)
cy.visit(`http://calendar.whatever/ICN&markettype=${data.market}`)
const outsideHistory = dayjs().subtract(data.days+1, 'days')
.format('D') // no leading '0'
const insideHistory = dayjs().subtract(data.days, 'days')
.format('D') // no leading '0'
const outsideHistoryRegex = new RegExp(`^${outsideHistory}`) // ^ = startsWith
const insideHistoryRegex = new RegExp(`^${insideHistory}`)
cy.get('.calendar-table').click
cy.get('.calendar-table').contains('td', outsideHistoryRegex)
.last()
.should("have.class","disabled")
cy.get('.calendar-table').contains('td', insideHistoryRegex)
.last()
.should("have.class","enabled")
}
I'm assuming you only want to check the history boundary for each market, but if you want to check multiple dates per market
const history = [
{ market: 'ICN', days: 85 },
{ market: 'ICN', days: 100 },
{ market: 'ICN', days: 365 },
{ market: 'ZYX', days: 120 },
...
]
// Same function...
You can do something like this:
cy.get('.calendar-table')
.find('td')
.then(($ele) => {
if ($ele.text().includes(todaysDate3)) {
cy.wrap($ele).should('have.class', 'disabled')
//Do Something
} else if ($ele.text().includes(todaysDate4)) {
cy.wrap($ele).should('have.class', 'enabled')
//Do Something
} else {
//Do something
}
})
I'm creating data per day and I'm dealing with following response data ...
{
tipster: {
name: "Gallita FC",
description: "TEST",
picks: [{
date: "Friday, February 18th 2022",
data: [{
title: "yesterday",
description: "TEST",
date: "Friday, February 18th 2022",
category: "NHL",
pickImageUrl: "https://res.cloudinary.com/creaciones-inteligentes-roy/image/upload/v1644455039/Captura_de_Pantalla_2022-02-09_a_la_s_18.59.43_voy1pj.png",
}],
}, {
date: "Saturday, February 19th 2022",
data: [{
title: "today",
description: "TEST",
date: "Saturday, February 19th 2022",
category: "NHL",
pickImageUrl: "https://res.cloudinary.com/creaciones-inteligentes-roy/image/upload/v1644455039/Captura_de_Pantalla_2022-02-09_a_la_s_18.59.43_voy1pj.png",
}],
}],
imageUrl: "https://res.cloudinary.com/sports-master/image/upload/v1644649610/27ADF778-454B-4DB7-88B7-DC98202E2736_utb7xw.png",
bannerUrl: "https://scontent.fmex34-1.fna.fbcdn.net/v/t1.6435-9/167022015_1317341031983063_7337313589197318410_n.jpg?_nc_cat=111&ccb=1-5&_nc_sid=a26aad&_nc_ohc=5ctqP2nFf7IAX94PNSO&_nc_ht=scontent.fmex34-1.fna&oh=00_AT_TzRHhhV73ji7wzW2X1u27TOU8TNlObwtp0ILc0DzC1Q&oe=62207F2C",
id: "62075e5a13a43ace611fe5bd",
},
}
Within the tipster.picks array I need to append an additional data item to the last matching data item. A match could be where data.title equals "today".
The code I came up with so far does not lead to the correct result ...
const newPick = {
title,
description,
date,
category,
pickImageUrl,
};
const tipsterUpdate = {
...req.body,
picks: [...tipster.picks, tipster.picks.slice(-(1)[0], newPick)],
};
I'm using spread operator because I need to maintain the old data and only add a new object on the data array.
I really appreciate a little help here.
Thank you.
Destructure out the picks array from everything else in the tipster object, then build a new tipster object containing an updated picks array.
const data={tipster:{name:"Gallita FC",description:"TEST",picks:[{date:"Friday, February 18th 2022",data:[{title:"yesterday",description:"TEST",date:"Friday, February 18th 2022",category:"NHL",pickImageUrl:"https://res.cloudinary.com/creaciones-inteligentes-roy/image/upload/v1644455039/Captura_de_Pantalla_2022-02-09_a_la_s_18.59.43_voy1pj.png"}]},{date:"Saturday, February 19th 2022",data:[{title:"today",description:"TEST",date:"Saturday, February 19th 2022",category:"NHL",pickImageUrl:"https://res.cloudinary.com/creaciones-inteligentes-roy/image/upload/v1644455039/Captura_de_Pantalla_2022-02-09_a_la_s_18.59.43_voy1pj.png"}]}],imageUrl:"https://res.cloudinary.com/sports-master/image/upload/v1644649610/27ADF778-454B-4DB7-88B7-DC98202E2736_utb7xw.png",bannerUrl:"https://scontent.fmex34-1.fna.fbcdn.net/v/t1.6435-9/167022015_1317341031983063_7337313589197318410_n.jpg?_nc_cat=111&ccb=1-5&_nc_sid=a26aad&_nc_ohc=5ctqP2nFf7IAX94PNSO&_nc_ht=scontent.fmex34-1.fna&oh=00_AT_TzRHhhV73ji7wzW2X1u27TOU8TNlObwtp0ILc0DzC1Q&oe=62207F2C",id:"62075e5a13a43ace611fe5bd"}};
const newPick = {
title: 'Bob',
description: 'Bob does it again',
date: new Date(),
category: 'Bob',
pickImageUrl: 'bobImage',
};
// Accept data, the new pick, and a search
// (in this case "today")
function addNewPick(data, newPick, search) {
// Grab the picks, and then everything else
// from the tipster object
const { tipster: { picks, ...rest } } = data;
// `find` the index of the array containing the search text
const index = picks.findIndex(pick => {
return pick.data.some(obj => {
return obj.title === search;
});
});
// Add the new pick to the "today" array
picks[index].data.push(newPick);
// Return a new tipster object with
// the updated picks
return {
tipster: { ...rest, picks }
};
}
const out = addNewPick(data, newPick, 'today');
console.log(out);
quoting the OP
I'm using spread operator because I need to mantaint the old data and only add a new object on the data array.
Since spread syntax creates a shallow copy only, thus any nested level of the copy is still a reference and therefore in danger of being mutated, I suggest a one time deep clone via structuredClone (there are polyfills for environments which do not yet support this Web-Api method).
And as for a generic approach, which inserts a new data item after (either) the last data with a matching condition (or even after every condition matching data item), one needs a function which gets provided
the tipster.picks reference of the deeply cloned response data object,
the to be inserted new data item,
a callback function which implements the condition of a matching data item.
Within a first step one would collect a list of all data items where the condition does match. The second step is the insert task which can be adapted to maybe changing requirements ...
function insertDataItemAfterLastMatchingCondition(picks, item, condition) {
// collect a list of all data items where `condition` matches.
const matchList = picks
.reduce((matches, pickItem) => {
const { data } = pickItem;
const index = data.findIndex(condition);
if (index >= 0) {
matches.push({ array: data, index });
}
return matches;
}, []);
// insert new item excusivley after the last matching data item.
const { array, index } = matchList.at(-1) ?? {};
if (Array.isArray(array)) {
array.splice((index + 1), 0, item);
}
// // insert new item (copy) after every matching data item.
//
// matchList.forEach(({ array, index }) =>
// array.splice((index + 1), 0, {...item})
// );
}
const responseData = {tipster:{name:"Gallita FC",description:"TEST",picks:[{date:"Friday, February 18th 2022",data:[{title:"yesterday",description:"TEST",date:"Friday, February 18th 2022",category:"NHL",pickImageUrl:"https://res.cloudinary.com/creaciones-inteligentes-roy/image/upload/v1644455039/Captura_de_Pantalla_2022-02-09_a_la_s_18.59.43_voy1pj.png"}]},{date:"Saturday, February 19th 2022",data:[{title:"today",description:"TEST",date:"Saturday, February 19th 2022",category:"NHL",pickImageUrl:"https://res.cloudinary.com/creaciones-inteligentes-roy/image/upload/v1644455039/Captura_de_Pantalla_2022-02-09_a_la_s_18.59.43_voy1pj.png"}]}],imageUrl:"https://res.cloudinary.com/sports-master/image/upload/v1644649610/27ADF778-454B-4DB7-88B7-DC98202E2736_utb7xw.png",bannerUrl:"https://scontent.fmex34-1.fna.fbcdn.net/v/t1.6435-9/167022015_1317341031983063_7337313589197318410_n.jpg?_nc_cat=111&ccb=1-5&_nc_sid=a26aad&_nc_ohc=5ctqP2nFf7IAX94PNSO&_nc_ht=scontent.fmex34-1.fna&oh=00_AT_TzRHhhV73ji7wzW2X1u27TOU8TNlObwtp0ILc0DzC1Q&oe=62207F2C",id:"62075e5a13a43ace611fe5bd"}};
const responseClone = (typeof structuredClone === 'function')
&& structuredClone(responseData)
|| JSON.parse(JSON.stringify(responseData)); // fallback
const newData = {
title: 'tomoorow',
description: 'TEST',
date: 'Sunday, February 20th 2022',
category: 'NHL',
pickImageUrl: 'https://res.cloudinary.com/creaciones-inteligentes-roy/image/upload/v1644455039/Captura_de_Pantalla_2022-02-09_a_la_s_18.59.43_voy1pj.png',
};
insertDataItemAfterLastMatchingCondition(
responseClone.tipster.picks,
newData,
data => data.title === 'today',
);
console.log({ responseData, responseClone });
.as-console-wrapper { min-height: 100%!important; top: 0; }
I have a reduce function that is building multiple levels and is working perfectly for me except for one issue
Currently, it's building data based on employee first, then by date, area, and job. I'm getting all of the data at the proper level but I'm now trying to aggregate certain data for a totals section at the date level and it's just listing values rather than aggregating.
Basically, in the line I've notated below, I'd like to create a value called total_scans that simply adds up ALL scans for any orders on that date. In other words, for the record for Miranda on 8/12 I would expect the total_scans at the date level to have 49 as the value. Am I on the right track?
const nest = (rows) =>
rows.reduce(
(a, row) => {
const employee = a[row.employee] || (a[row.employee] = { dates: {} })
const date = employee.dates[row.job_date] || (employee.dates[row.job_date] = { areas: {} })
const order = date.areas[row.area_number] || (date.areas[row.area_number] = { jobs: {} })
const job = order.jobs[row.job] || (order.jobs[row.job] = { hours: '', scans: '', job_date: '' })
job.hours += row.hours
job.scans += row.scans
job.job_date = row.job_date
//this line is my issue
date.total_scans += job.scans
return a
},
{}
);
new Vue({
el: "#app",
props: {
},
data: {
rows: [
{
employee: "Miranda",
job: "123",
hours: "10",
job_date: "08/12/2021",
scans: 37,
area_number: "1234567",
},
{
employee: "Miranda",
job: "167",
hours: "15",
scans: 12,
job_date: "08/12/2021",
area_number: "1234568",
},
{
employee: "Miranda",
job: "184",
hours: "18",
scans: 24,
job_date: "08/13/2021",
area_number: "1234569",
}
],
},
computed: {
numbersByEmployee() {
return nest(this.rows)
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
{{numbersByEmployee}}
</div>
Your usage of reduce is a little irregular. The idea of reduce is to take an iterable (array) and return a single value, usually something like a String or Number.
Also, you're causing all sorts of side effects in your reducer, by modifying the object and arrays. Since Javascript is pass-by-reference for arrays and objects, those changes you're causing will be reflected in the original object, which is not how Vue prescribes things are done. If you want to modify data, it should be done in a watch, not a computed.
Finally, I believe you're overcomplicating your reduce function. Instead of a long reduce like that, you could simply do the below. Note the initialValue of 0.
const nest = (rows) =>
rows.reduce(
(sum, row) => {
return sum + row['scans'];
},
0
);
Obviously this will count all scans. If you want to only count scans by date, how about save yourself running the reducer across the array, and instead run filter first? Something like
const nest = (rows) =>
rows.filter(({job_date}) => job_date === SomeDate).reduce(...)
The {job_date} is a destructuring assignment. You could also split out a date filtered array into its own computed.
I've a usecase in which I need to find the data of a particular month. How to get the start and end dates of given month?
Here's the sample code.
{"_id":"5e00bc55c31ecc38d023b156","heat":20,"humidity":10,"deviceId":"a-1","template":"13435158964","entryDayTime":"2019-12-23T13:08:37.841Z"},
{"_id":"5e00bbd2c31ecc38d023b155","heat":20,"humidity":10,"deviceId":"a-1","template":"13435158964","entryDayTime":"2019-12-23T13:06:26.366Z"},
{"_id":"5df4a8fb46b9da1e2c0731df","heat":88,"humidity":80,"deviceId":"a-1","template":"13435158964","entryDayTime":"2019-12-14T09:18:51.892Z"},
{"_id":"5e00b50bc127260398cf51dd","heat":20,"humidity":10,"deviceId":"a-1","template":"13435158964","entryDayTime":"2019-12-23T12:37:31.127Z"},
{"_id":"5df20e44e7c51b4bd0095af3","heat":41,"humidity":26,"deviceId":"a-1","template":"13435158964","entryDayTime":"2019-12-12T09:54:12.375Z"}
Here's my code without moment.js
Payload:
{
"deviceId":"a-1",
"year":2019,
"month":"December"
}
Collection.aggregate([
{
$match: {
"deviceId": payload.deviceId,
"entryDayTime": {
$lt: new Date(`${payload.month},${payload.year},2`).toISOString(),
$gte: new Date(`${payload.month},${payload.year},31`).toISOString()
}
}
}
])
These are the time ranges I'm getting in console(times passed in aggregate function),
2019-12-01T18:30:00.000Z
2019-12-30T18:30:00.000Z
Code with moment.js
Payload:
{
"deviceId":"a-1",
"year":2019,
"month":10
}
I've tried with moment.js too. But I'm not getting the times in the format like time format of database.
Collection.aggregate([
{
$match: {
"deviceId": payload.deviceId,
"entryDayTime": {
$lt:moment([payload.year]).month(payload.month).startOf('month').tz('Asia/Kolkata').format(),
$gte:moment([payload.year]).month(payload.month).endOf('month').tz('Asia/Kolkata').format()
}
}
}
])
Following are the timestamps I'm getting in console.
2019-11-01T00:00:00+05:30
2019-11-30T23:59:59+05:30
If moment.js is preferred, how to change the time format similar to the sample code's time format?
Just try this code:
var dated="2019-11-01T00:00:00+05:30";
var newdated= new Date(dated);
var output= newdated.toISOString();
console.log(output);
Result :
'2019-10-31T18:30:00.000Z'
The toISOString() method returns a string in simplified extended ISO format (ISO 8601), which is always 24 or 27 characters long (YYYY-MM-DDTHH:mm:ss.sssZ or ±YYYYYY-MM-DDTHH:mm:ss.sssZ, respectively).
The timezone is always zero UTC offset, as denoted by the suffix "Z".
To find the data of a particular month use Date.UTC with the Date constructor to create a range:
const payload = {
"deviceId": "a-1",
"year": 2019,
"month": 11 // months start from 0 = January, so 11 = December
}
const from = new Date(Date.UTC(payload.year, payload.month, 1)).toISOString(); // "2019-12-01T00:00:00.000Z"
const to = new Date(Date.UTC(payload.year, payload.month + 1, 1)).toISOString(); // "2020-01-01T00:00:00.000Z"
then use them as follows:
Collection.aggregate([
{
$match: {
"deviceId": payload.deviceId,
"entryDayTime": {
$lt: to,
$gte: from
}
}
}
])
Working example : https://mongoplayground.net/p/jkIJdJ-L7q-