0

a couple of classic disclaimers first (and a Hello everyone, because I don't know why it keeps cutting these 2 words if I put them at the beginning!):

  • Super newbie here, started learning Javascript 10 days ago;
  • I searched a lot for a solution to my problem, but after more than 2 days trying solutions, I open this post, a sort of painful surrender.

My problem: basically, from a Json which contains a lot of records in the format {"date": "MM-DD-YYYY", "temperature": integer} I am trying to build a sort of interactive script that will take all the different months in that json, and will automatically give me the average temperature for that month. So if you change the array and add 1 month of records, immediately I get also the new average for the new month.

So, starting from the Json, my steps are to make an array with all the months included in the file (not every month is included) with a map and new date... then I will transform the "javascript months" (which starts from 0... but in the Json january is not 0 but 1) to make an array of months included in "string form" where Jan = "01", Feb = "02" and so on.

After that I am lost: I have tried a lot of combination with loops, nested loops, forEach, but I can't figure out a way to say to Javascript: "for every item in this array of months, take all the relative records from the Json, and give me an Avg Temperature!"

Basically what I would like to achieve is simple: from a given Json in that format, I retrieve an array of the months, and the end results = an array of average temperatures, 1 avg temp for every month.

I'll try to post what I did until now (only the functioning parts)!

Thank you in advance!

const dates =   [
{"date": "01-24-2020", "temps": 1},
{"date": "01-31-2020", "temps": -1},
{"date": "02-01-2020", "temps": 6},
{"date": "02-02-2020", "temps": 2},
{"date": "03-03-2020", "temps": 1},
{"date": "03-04-2020", "temps": 1},
{"date": "04-06-2020", "temps": -2},
{"date": "04-08-2020", "temps": -4}]

const singleMonths = Array.from(new Set(dates.map(elem => new Date(elem.date).getMonth())))

const singleMonthString = singleMonths.map(m => `${"0"+(m+1)}`)

//after this I tried A LOT of different solutions, but I can only filter manually for a singular item in my Array, and not how I would like to, automatically for all my items in my array!

const damnFilter = dates.filter(m => m.dates.substring(0,2)==result[0]) 



CodingBad
  • 3
  • 1
  • you say January is month `0`, so February is month `1`. then why does February have `31` days in your example? `{ date: '01-31-2020', temps: -1 }` – Mister Jojo Feb 26 '22 at 22:26
  • Sorry, maybe I wasn't clear in my post: if I'm not mistaken (which I may very well be) in Javascript Date object January = 0, and February is 1, and so on... so my const singleMonths will start from 0 because there is a January inside my Json. But in the Json the months are "real life months". January = 01, Feb = 02, and so on. I hope this was clearer! – CodingBad Feb 26 '22 at 22:30
  • You don't have to use `new Date(elem.date).getMonth()` to get the correct month value. A simple `elem.date.split('-')` is enough. And you don't need to explain how getMonth() method is, 99% of those answering here already know that – Mister Jojo Feb 26 '22 at 23:10
  • `new Date(elem.date)` is a bad idea and very likely to produce an invalid date in some browsers, see [*Why does Date.parse give incorrect results?*](https://stackoverflow.com/questions/2587345/why-does-date-parse-give-incorrect-results). Note that `new Date(string)` is identically equivalent to `new Date(Date.parse(string))`. – RobG Feb 27 '22 at 04:45

2 Answers2

0

I don't know if it is that you want, but here I created some of the snippet, where it gets four months and averages each month temperature:

let newDates = [];
for(let i = 1; i <= 12; i++){
    newDates = dates.filter(date => new Date(date.date).getMonth() + 1 == i)
    if(newDates.length > 0){
    let accumulator = 0;
    newDates.map(day => accumulator += day.temps);
    console.log("Average: ", accumulator / newDates.length);
    }
}
Mantofka
  • 206
  • 1
  • 12
0

Some comments on your code:

const singleMonths = Array.from(new Set(dates.map(elem => new Date(elem.date).getMonth())))

is quite inefficient. The data is already an array, there's no need to create a new array through map, convert it to a set, then back to another array. Also, there's no need to create a Date to get the month, not to mention the inefficiency of doing the +1 via another map when it could be done in the above line (if it was needed).

The format dd-mm-yyyy is not supported by ECMAScript so parsing is implementation dependent. Safari at least treats it as an invalid date, see Why does Date.parse give incorrect results?

const singleMonthString = singleMonths.map(m => `${"0"+(m+1)}`)

If the +1 was needed, better to do it in the first map, and even if you need to do it, why create another array? Just use forEach and modify the existing array.

const damnFilter = dates.filter(m => m.dates.substring(0,2)==result[0]) 

result isn't declared or initialised anywhere so that's not going anywhere.

You can use reduce on the data array to sum the temperatures for a particular month and generate an average. It's convenient to keep the values for sum and count for a month in the accumulator that is passed to each subsequent loop. You can then generate a new average each time or do it at the end. Calculating the average each time is simpler but possibly less efficient if the data file is large. But for a small file the difference will be negligible.

let data =   [
  {"date": "01-24-2020", "temps": 1},
  {"date": "01-31-2020", "temps": -1},
  {"date": "02-01-2020", "temps": 6},
  {"date": "02-02-2020", "temps": 2},
  {"date": "03-03-2020", "temps": 1},
  {"date": "03-04-2020", "temps": 1},
  {"date": "04-06-2020", "temps": -2},
  {"date": "04-08-2020", "temps": -4}
];

let averageData = data.reduce((acc, obj) => {
  // Get month number
  let month = obj.date.slice(0,2);
  // If accumulator doesn't have a summary object for
  // that month, add it with initial values
  if (!acc[month]) {
    acc[month] = {sum:0, count:0, year: obj.date.slice(-4)};
  }
  // Update summary values for the month
  acc[month].sum += obj.temps;
  acc[month].count += 1;
  acc[month].average = acc[month].sum / acc[month].count;
  // Return the accumulator
  return acc;
  // Use a completely emtpy object for the accumulator
}, Object.create(null));

// Show averageData object
console.log(averageData);

// Show data as month : average
Object.keys(averageData).forEach(key => console.log(
  new Date(averageData[key].year, key-1).toLocaleString('en',{month:'short', year:'numeric'}) +
  ': ' + averageData[key].average
));
RobG
  • 142,382
  • 31
  • 172
  • 209
  • Thank you RobG for all the precious comments on my code, and for this magnificent solution. I learned a lot with trying to "scramble up a recipe" for this project, but I learned a lot more seeing your use of methods (especially .reduce, which was very low in my consideration!) The result of your code is like 3 times more elegant than what I had in mind! Thank you again. – CodingBad Feb 27 '22 at 10:00