Search code examples
javascripttypescriptalgorithmsortingdata-structures

Calculating Number of Farms Needed for Chicken Housing with Rotational Barn Usage in TypeScript


Long story short, I have an xlsx file that has the following data shape, but with a lot more data.

Date Barns
14/May/2024 13
15/May/2024 14
16/May/2024 16

The file might contain 200-300 entries. It represents the number of chickens that come out of the egg hatcher farm by barns (each barn has 33000 chicken).

I managed to convert these data into JSON and then I stored them in my database, but now I need to find how many farms I need to house these chickens.

Here they have some rules, each farm can house 16 barns, and each barn can keep the chickens for 33 days and then sell them. After selling them a barn needs to be empty for 22 days after being used again.

My problem is I can't find a way to calculate how many farms I need when each farm is going to be used.

This is the code I have right now:

  const settings = { BARNS_PER_FARM: 16 }

  const totalBarns = planDays.reduce((acc, day) => acc + day.barns, 0)
  const firstDate = planDays[0]?.date
  const lastDate = planDays[planDays.length - 1]?.date
  const farmsNeeded = Math.ceil(totalBarns / settings.BARNS_PER_FARM)

  const copy = [...planDays]

  const farms: Farm[] = Array.from({ length: farmsNeeded }).map((_, i) => {
    return {
      name: `Farm ${i + 1}`,
      housing: [],
      lastUsedDate: null, // Track last used date for reusability
    }
  })

  const current = {
    farm: 0,
  }

  for (const day of copy) {
    const barns = day.barns
    if (barns === 0) {
      continue
    }

    const farm = farms[current.farm]

    const available = settings.BARNS_PER_FARM - farmUsedBarns(farm)

    const needed = barns

    if (available >= needed) {
      addHousing(farm, needed, day.date)
    }
    else {
      if (available > 0) {
        addHousing(farm, available, day.date)
      }

      let remaining = needed - available
      while (remaining > 0) {
        current.farm++
        const farm = farms[current.farm]
        const available = settings.BARNS_PER_FARM - farmUsedBarns(farms[current.farm])

        const needed = remaining
        if (available >= needed) {
          addHousing(farm, needed, day.date)
          remaining = 0
        }
        else {
          addHousing(farm, available, day.date)
          remaining -= available
        }
      }
    }
  }

  return {
    status: 200,
    body: {
      data: {
        farms: farms.filter(farm => farm.housing.length > 0),
        totalBarns,
        firstDate,
        lastDate,
        farmsNeeded,
      },
    },
  }

function addHousing(farm: Farm, numberOfBarns: number, startDate: Date) {
  if (farm.housing.length === 0) {
    farm.housing.push({
      name: 'A',
      startDate: startDate,
      numberOfBarns,
    })
    return
  }
  const previousHousing = farm.housing[farm.housing.length - 1]
  const newStartDate = new Date(previousHousing.startDate)
  newStartDate.setDate(newStartDate.getDate() + 1)

  farm.housing.push({
    name: String.fromCharCode(previousHousing.name.charCodeAt(0) + 1),
    numberOfBarns,
    startDate: startDate,
  })
}

function farmUsedBarns(farm: Farm) {
  return farm.housing.reduce((acc, housing) => {
    return acc + housing.numberOfBarns
  }, 0)
}

It doesn't take into account that the barn can be used after 56 days. and keeps adding farms when the previous one is filled.


Solution

  • const settings = { 
            BARNS_PER_FARM: 16 ,
            //with this parameter you can set the 33 days and the 22 days 
            //requierd for free your housing space
            //i lower the days required for free your space because 
            //i need to reproduce the event with my small set of demo data ^_^
            HOLDING_DAYS:3,//set to 33 for real use
            SELLING_DAYS:2,//set to 22 for real use
        }
    
    
        //small set of fake data
        const planDays=[
            {date:"14/May/2024",barns:13},
            {date:"15/May/2024",barns:14},
            {date:"16/May/2024",barns:16},
            {date:"17/May/2024",barns:13},
            {date:"18/May/2024",barns:14},
            {date:"19/May/2024",barns:16},
            {date:"20/May/2024",barns:13},
            {date:"21/May/2024",barns:14},
            {date:"22/May/2024",barns:16},
        ];
    
      const totalBarns = planDays.reduce((acc, day) => acc + day.barns, 0)
      const firstDate = planDays[0]?.date
      const lastDate = planDays[planDays.length - 1]?.date
      const farmsNeeded = Math.ceil(totalBarns / settings.BARNS_PER_FARM)
    
      const copy = [...planDays]
    
      const farms = Array.from({ length: farmsNeeded }).map((_, i) => {
        return {
          name: `Farm ${i + 1}`,
          housing: [],
                //since you are splitting day.barns in one or more farm  lastUsedDate is not usefull
                //because reusability is binded to barns and not to farm
                //==========================================================
          lastUsedDate: null, // Track last used date for reusability
                //==========================================================
        }
      });
        console.log("farms",farms);
    
      const current = {
        farm: 0,
      }
        
        var result ={
            farms:[],
            barnsOnHousing:0,
            barnsFromStart:0,
            maxFarmsUsed:0,
        }
    
      for (const day of copy) {
        const barns = day.barns
            
            //every day it does not matter if there are barns planned or not
            //you need to update your farms status 
            //(the good Store Manager is the one who does the inventory every day ^_^ )
            updateFarmsStatus(day.date);
            
            //plus every day you need to check for free space from the first farm
            //because every day a farm could have some new free space.
            current.farm=0;
            
        if (barns === 0) {
          continue
        }
    
        const farm = farms[current.farm]
    
        const available = settings.BARNS_PER_FARM - farmUsedBarns(farm)
    
        const needed = barns
    
        if (available >= needed) {
          addHousing(farm, needed, day.date)
        }
        else {
          if (available > 0) {
            addHousing(farm, available, day.date)
          }
    
          let remaining = needed - available
          while (remaining > 0) {
            current.farm++
            const farm = farms[current.farm]
            const available = settings.BARNS_PER_FARM - farmUsedBarns(farms[current.farm])
    
            const needed = remaining
            if (available >= needed) {
              addHousing(farm, needed, day.date)
              remaining = 0
            }
            else {
              addHousing(farm, available, day.date)
              remaining -= available
            }
          }
        }
            
            //this is just for log your day by day farms status:
            //========================================================
            result.barnsOnHousing+=barns;
            result.barnsFromStart+=barns;
            result.farms=farms.filter(farm => farm.housing.length > 0);
            if(result.maxFarmsUsed<result.farms.length){
                result.maxFarmsUsed=result.farms.length;
            }
            console.log("day:",day.date);
            console.log("result:",result);
            //========================================================
      }
        
    /*
        return  {
                status: 200,
                body: {
                    data: {
                        farms: farms.filter(farm => farm.housing.length > 0),
                        totalBarns,
                        firstDate,
                        lastDate,
                        farmsNeeded,
                    },
                },
            }
    */
    
        //final log in replace of return:
        result.totalBarns=totalBarns;
        result.firstDate=firstDate;
        result.lastDate=lastDate;
        result.farmsNeeded=farmsNeeded;
        console.log("====================");
        console.log("===FINAL REPORT=====");
        console.log("====================");
        console.log("result:",result);
    
      
    function addHousing(farm, numberOfBarns, startDate) {
      if (farm.housing.length === 0) {
        farm.housing.push({
          name: 'A',
          startDate: startDate,
          numberOfBarns,
        })
        return
      }
      const previousHousing = farm.housing[farm.housing.length - 1]
      //never used:
        //const newStartDate = new Date(previousHousing.startDate)
      //newStartDate.setDate(newStartDate.getDate() + 1)
    
      farm.housing.push({
        name: String.fromCharCode(previousHousing.name.charCodeAt(0) + 1),
        numberOfBarns,
        startDate: startDate,
      })
    }
    
    function farmUsedBarns(farm) {
      return farm.housing.reduce((acc, housing) => {
        return acc + housing.numberOfBarns
      }, 0)
    }
    
    //this is the function that do the magic
    function updateFarmsStatus(currentDate){
        const cDate= new Date(currentDate);
        result.barnsOnHousing=0;
        farms.forEach(_farm=>{
            _farm.housing = _farm.housing.filter(function(_housing) {
                
                var sDate = new Date(_housing.startDate);
                sDate.setDate(sDate.getDate() + parseInt(settings.HOLDING_DAYS+settings.SELLING_DAYS));
                //console.log("date",[sDate,cDate,cDate<sDate]);
                let res=cDate<sDate;
                if(res){
                    result.barnsOnHousing+=_housing.numberOfBarns;
                }
                return res;
                
            });
        });
        
    }