'Distribute items evenly but add remainder to first

The snippet below pretty much says it all, but in short I need to distribute a certain amount of months equally over activities. Since there is always a chance to deal with a remainder these should be added to the first month.

const selectedMonth = 5
const project = {
  duration: 2, // in months
  activities: [{
      number: 1,
      title: 'game 1'
    },
    {
      number: 2,
      title: 'game 2'
    },
    {
      number: 3,
      title: 'game 3'
    },
  ]
}

// 1 Add a "plannedInMonth" property to each activity
// 2 Start planning from the selected month and onwards (the month number can be > 12)
// 3 Spread the activities evenly based on the duration
function planActivitiesInMonths() {}
planActivitiesInMonths()

// So this function should, since the remainder of 3 / 2 = 1, return as follows:
activities: [{
    number: 1,
    title: 'game 1',
    plannedInMonth: 5
  },
  {
    number: 2,
    title: 'game 2',
    plannedInMonth: 5

  },
  {
    number: 3,
    title: 'game 3',
    plannedInMonth: 6

  },
]

// However, it should also work when e.g. 24 activities need to be distributed across 5 months


Solution 1:[1]

If you're just looking to copy paste an implementation of the algorithm, this should do it:

function planActivitiesInMonths(project, selectedMonth) {
    const remainder = project.activities.length % project.duration
    const activitesPerMonth = Math.floor(project.activities.length / project.duration)
    
    return project.activities.map((activity, i) => {
        let index = Math.floor((i - remainder) / activitesPerMonth)
        if (index < 0) {
            index = 0
        }
        
        activity.plannedInMonth = index + selectedMonth
        return activity
    })
}

Just keep in mind that my function returns a value and doesn't mutate directly the object.

I am shifting the index by the remainder to have to be able to nicely handle the fact that the remainder activities should be added to the first month, but there are a tons of ways of implementing this algorithm.


However, this algorithm has a strange behaviour if the project duration is slightly below a multiple of the activities per month. In this case, the remainder would be very big and a lot of activities would be added to the first month.

For example, if you want to distribute 9 activities across 5 months, the remainder would be 5 % 9 = 4, so the first month would have a total of 5 activities!

Maybe it's better to evenly distribute the remainder too. And this algorithm has a cleaner and simpler implementation:


function planActivitiesInMonths(project, selectedMonth) {
    const activitesPerMonth = project.activities.length / project.duration
    
    return project.activities.map((activity, i) => {
        const index = Math.floor(i / project.activities.length * activitesPerMonth)
        activity.plannedInMonth = index + selectedMonth
        return activity 
    })
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1