1

I'm trying to group an array by one property (year) and then create a nested array inside each year, listing the weeks with their total Total Distance and Max Avg Speed.

i.e.

[
    {
        "year": 2015,
        "weeks": [
            {week: 45, totalDistance: nnn, maxAvgSpeed: nnn},
            {week: 46, totalDistance: nnn, maxAvgSpeed: nnn},
            {week: 47, totalDistance: nnn, maxAvgSpeed: nnn}
            ...

This is what I have so far, but it says "activitiesByYear.forEach is not a function" even though activitiesByYear is an array as expected.

const activities = [
    {
        "id": 1,
        "actvityYear": 2015,
        "actvityWeek": 45,
        "name": "One",
        "avgSpeed": 1200,
        "distance": 2

    },
    {
        "id": 2,
        "actvityYear": 2015,
        "actvityWeek": 45,
        "name": "Two",
        "avgSpeed": 1403,
        "distance": 6
    },
    {
        "id": 3,
        "actvityYear": 2015,
        "actvityWeek": 46,
        "name": "Three",
        "avgSpeed": 1700,
        "distance": 7
    },
    {
        "id": 4,
        "actvityYear": 2015,
        "actvityWeek": 47,
        "name": "Four",
        "avgSpeed": 600,
        "distance": 12
    },
    {
        "id": 5,
        "actvityYear": 2015,
        "actvityWeek": 47,
        "name": "Five",
        "avgSpeed": 300,
        "distance": 4
    },
    {
        "id": 6,
        "actvityYear": 2016,
        "actvityWeek": 2,
        "name": "Six",
        "avgSpeed": 1800,
        "distance": 15
    }
]

function groupBy(objectArray, property) {
    return objectArray.reduce((acc, obj) => {
    const key = obj[property];
    if (!acc[key]) {
        acc[key] = [];
    }
    // Add object to list for given key's value
    acc[key].push(obj);
    return acc;
    }, {});
}

const activitiesByYear = groupBy(activities, 'actvityYear');
let years = [];
activitiesByYear.forEach(year => {
    let weeks = [];
    year.reduce(function(result, value) {
        const week = moment(value.actvityDate).week();
        if (!result[week]) {
            result[week] = { week: week, totalDistance: 0, maxAvgSpeed: value.avgSpeed };
            weeks.push(result[week])
        }
        result[week].totalDistance += value.distance;
        if (value.average_speed > result[week].maxAvgSpeed) result[week].maxAvgSpeed = value.average_speed;
        return result;
    }, {});
    years.push({"year": year, "weeks": weeks})
})

console.log(years)

2 Answers 2

1

Use Object#entries to iterate over the year-items pairs.

Solution after some enhancements:

  • In groupBy helper, we use Array#reduce to iterate over the object array to group its items by the property param which is actvityYear
  • Using Object#entries, get the list of grouped year-items list from the above
  • Using Array#reduce, iterate over this list of pairs while updating a list of resulting years
    • In each iteration, we use Array#reduce to iterate over the current year's items while updating an object where the week is the key and the grouped week-items array is the value.
    • Use Object#values to get the resulting week-items of this year and push a new item to the resulting years array

const activities = [
  { "id": 1, "actvityYear": 2015, "actvityWeek": 45, "name": "One", "avgSpeed": 1200, "distance": 2 },
  { "id": 2, "actvityYear": 2015, "actvityWeek": 45, "name": "Two", "avgSpeed": 1403, "distance": 6 },
  { "id": 3, "actvityYear": 2015, "actvityWeek": 46, "name": "Three", "avgSpeed": 1700, "distance": 7 },
  { "id": 4, "actvityYear": 2015, "actvityWeek": 47, "name": "Four", "avgSpeed": 600, "distance": 12 },
  { "id": 5, "actvityYear": 2015, "actvityWeek": 47, "name": "Five", "avgSpeed": 300, "distance": 4 },
  { "id": 6, "actvityYear": 2016, "actvityWeek": 2, "name": "Six", "avgSpeed": 1800, "distance": 15 }
];

function groupBy(objectArray, property) {
  return objectArray.reduce((acc, obj) => {
    const key = obj[property];
    if (!acc[key]) { acc[key] = []; }
    acc[key].push(obj);
    return acc;
  }, {});
}

const activitiesByYear = groupBy(activities, 'actvityYear');
const years = Object.entries(activitiesByYear).reduce((years, [year, items]) => {
  const weeks = Object.values(
    items.reduce((weeks, { actvityWeek: week, distance, avgSpeed }) => {
      if (!weeks[week]) {
        weeks[week] = { week, totalDistance: 0, maxAvgSpeed: avgSpeed };
      }
      weeks[week].totalDistance += distance;
      if (avgSpeed > weeks[week].maxAvgSpeed) {
        weeks[week].maxAvgSpeed = avgSpeed;
      }
      return weeks;
    }, {})
  );
  years.push({ year, weeks });
  return years;
}, []);

console.log(years);

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks @Majed. I think its closer, but I'm getting just one week in each year. I think its because I need to push to result, not weeks, but that is problematic, I think because result is an Object.keys(). I could just use weeks and drop result, but that seems to abandon the purpose of reduce.
@Chris I added some enhancements and clarifications, feel free to ask any question
Thanks @Majed, this is the best explanation of reduce I've seen and the code works perfectly.
0

You get TypeError: activitiesByYear.forEach is not a function because activitiesByYear is a object with the following keys: [ '2015', '2016' ]. Because the groupBy returns an object. You can get the keys of the object with Object.keys().

For example:

const keys = Object.keys(activitiesByYear)
// [ '2015', '2016' ]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.