anon
anon

Reputation:

I'm struggling to solve a percentage computation

I'm ripping my hair out on the following so I'm kindly asking for help here.

I have an array of durations in minutes whose total is one day (1440 min.)
I have another array with a value which represent a protection level : 0 to 5.

var levels = new int[3];            
levels[0] = 0;
levels[1] = 5;
levels[2] = 0;

var minutes = new int[3];   
minutes[0] = 658; // 10 hours and 58 minutes
minutes[1] = 1; // 1 minute
minutes[2] = 781; // 13 hours and 1 minutes

In plain English we should read: Level 0 duration is 10 hours and 58 minutes, then Level 1 duration is 1 minute and finally we're back to level 0 for 13 hours and 1 minute. End of the day ;)

Of couse this is a simple example, this could be more richer than that, but I hope this helps understand the problem I'm trying to solve which is exposed below.

I need to compute an average of protection level for each hour of the day (from 0 to 23)\

The final result set would be an array of tuples with 6 items

var finalResult = new (int level0percent, int level1percent, int level2percent, int level3percent, int level4percent, int level5percent)[24];

With the data in this example that would mean:

A this point we have 2 minutes left (4%) to be given to Hour 10.

Still 2% (1 minute) to consume on the next level, which is 0 for now.

Then the remaining is now 13 hours for level 0 at index 11 to 23.

The tricky part for me is to process, in this case, the hour at index 10. May be I should restructure the data in a form that would be simplier to process, but I can't figure it out.

Any help appreciated.

PS : If Linq could be of any help, please do not hesitate to use it.


EDIT : Fixed the minutes array.

EDIT 2 : This what I've tried so far...

A level class to hold an intermediary result set:

public class Levels
{
    public int level0 { get; set; }


    public int level1 { get; set; }


    public int level2 { get; set; }


    public int level3 { get; set; }


    public int level4 { get; set; }


    public int level5 { get; set; }

}

The processing code so far... May not compile and/or work:

// Construct the final result to be sent back to the client
var averagesOn24HoursByHourNew = Enumerable.Range(0, 24).Select(i => new Levels()).ToArray();

// This index will allow us to advance each hour of the day (from 0 to 23)
int lastIndexOnAveragesOn24HoursByHourNew = 0;

// Used to gather the remaining minutes for incomplete hours
int lastMinuteLeftOnLastHour = 0;

for (var indexLevel = 0; indexLevel < levels.Count; indexLevel++)
{
    if (levels[indexLevel] == 0)
    {
        int durationInMinutes = (int)minutes[indexLevel];

        var nbMinutesLeft = durationInMinutes % 60;
        var nbHoursAtOneHundred = durationInMinutes / 60;
        if (lastMinuteLeftOnLastHour != 0)
        {
            averagesOn24HoursByHourNew[lastIndexOnAveragesOn24HoursByHourNew].level0 += lastMinuteLeftOnLastHour * 100 / 60;
            nbMinutesLeft -= lastMinuteLeftOnLastHour;
            lastMinuteLeftOnLastHour = 0;
        }

        int index = 0;
        for (index = lastIndexOnAveragesOn24HoursByHourNew; index < nbHoursAtOneHundred + lastIndexOnAveragesOn24HoursByHourNew; index++)
        {
#if DEBUG
            Debug.Assert(index <= 23);
#endif

            averagesOn24HoursByHourNew[index].level0 += 100;


            datiCountForOneHour[index] += 1;
        }
#if DEBUG
        // When th whole day is processed the index will be 24
        Debug.Assert(index < 25);
#endif
        if (nbMinutesLeft != 0)
        {
            averagesOn24HoursByHourNew[index].level0 += nbMinutesLeft * 100 / 60;
            if (lastMinuteLeftOnLastHour != 0 && lastMinuteLeftOnLastHour > nbMinutesLeft)
            {
                lastMinuteLeftOnLastHour -= nbMinutesLeft;
            }
            else
            {
                lastMinuteLeftOnLastHour = 60 - nbMinutesLeft;
            }
        }

        lastIndexOnAveragesOn24HoursByHourNew = index;
    }
    else if (levels[indexLevel] == 1)
    {
        int durationInMinutes = (int)minutes[indexLevel];

        // A duration of zero means less than one minute
        // set it to one minute
        if (durationInMinutes == 0)
        {
            durationInMinutes = 1;
        }
        else if (durationInMinutes == 1439)
        {
            // FIXME Add Missing minute if needed
            durationInMinutes = 1440;
        }

        var nbMinutesLeft = durationInMinutes % 60;

        var nbHoursAtOneHundred = durationInMinutes / 60;
        int index = 0;
        for (index = lastIndexOnAveragesOn24HoursByHourNew; index < nbHoursAtOneHundred + lastIndexOnAveragesOn24HoursByHourNew; index++)
        {
#if DEBUG
            Debug.Assert(index <= 23);
#endif
            if (lastMinuteLeftOnLastHour != 0)
            {
                averagesOn24HoursByHourNew[index].level1 += lastMinuteLeftOnLastHour * 100 / 60;
                lastMinuteLeftOnLastHour = 0;
            }
            else
            {
                averagesOn24HoursByHourNew[index].level1 += 100;
            }

            datiCountForOneHour[index] += 1;
        }
#if DEBUG
        // When th whole day is processed the index will be 24
        Debug.Assert(index < 25);
#endif
        if (nbMinutesLeft != 0)
        {
            averagesOn24HoursByHourNew[index].level1 += nbMinutesLeft * 100 / 60;
            if (lastMinuteLeftOnLastHour != 0 && lastMinuteLeftOnLastHour > nbMinutesLeft)
            {
                lastMinuteLeftOnLastHour -= nbMinutesLeft;
            }
        }

        lastIndexOnAveragesOn24HoursByHourNew = index;
    }
    else if (levels[indexLevel] == 2)
    {
        int durationInMinutes = (int)minutes[indexLevel];

        // A duration of zero means less than one minute
        // set it to one minute
        if (durationInMinutes == 0)
        {
            durationInMinutes = 1;
        }
        else if (durationInMinutes == 1439)
        {
            // FIXME Add Missing minute if needed
            durationInMinutes = 1440;
        }

        var nbMinutesLeft = durationInMinutes % 60;

        var nbHoursAtOneHundred = durationInMinutes / 60;
        int index = 0;
        for (index = lastIndexOnAveragesOn24HoursByHourNew; index < nbHoursAtOneHundred + lastIndexOnAveragesOn24HoursByHourNew; index++)
        {
#if DEBUG
            Debug.Assert(index <= 23);
#endif
            if (lastMinuteLeftOnLastHour != 0)
            {
                averagesOn24HoursByHourNew[index].level2 += lastMinuteLeftOnLastHour * 100 / 60;
                lastMinuteLeftOnLastHour = 0;
            }
            else
            {
                averagesOn24HoursByHourNew[index].level2 += 100;
            }

            datiCountForOneHour[index] += 1;
        }
#if DEBUG
        // When th whole day is processed the index will be 24
        Debug.Assert(index < 25);
#endif
        if (nbMinutesLeft != 0)
        {
            averagesOn24HoursByHourNew[index].level2 += nbMinutesLeft * 100 / 60;
            if (lastMinuteLeftOnLastHour != 0 && lastMinuteLeftOnLastHour > nbMinutesLeft)
            {
                lastMinuteLeftOnLastHour -= nbMinutesLeft;
            }
        }

        lastIndexOnAveragesOn24HoursByHourNew = index;
    }
    else if (levels[indexLevel] == 3)
    {
        int durationInMinutes = (int)minutes[indexLevel];

        // A duration of zero means less than one minute
        // set it to one minute
        if (durationInMinutes == 0)
        {
            durationInMinutes = 1;
        }
        else if (durationInMinutes == 1439)
        {
            // FIXME Add Missing minute if needed
            durationInMinutes = 1440;
        }

        var nbMinutesLeft = durationInMinutes % 60;

        var nbHoursAtOneHundred = durationInMinutes / 60;
        int index = 0;
        for (index = lastIndexOnAveragesOn24HoursByHourNew; index < nbHoursAtOneHundred + lastIndexOnAveragesOn24HoursByHourNew; index++)
        {
#if DEBUG
            Debug.Assert(index <= 23);
#endif
            if (lastMinuteLeftOnLastHour != 0)
            {
                averagesOn24HoursByHourNew[index].level3 += lastMinuteLeftOnLastHour * 100 / 60;
                lastMinuteLeftOnLastHour = 0;
            }
            else
            {
                averagesOn24HoursByHourNew[index].level3 += 100;
            }

            datiCountForOneHour[index] += 1;
        }
#if DEBUG
        // When th whole day is processed the index will be 24
        Debug.Assert(index < 25);
#endif
        if (nbMinutesLeft != 0)
        {
            averagesOn24HoursByHourNew[index].level3 += nbMinutesLeft * 100 / 60;
            if (lastMinuteLeftOnLastHour != 0 && lastMinuteLeftOnLastHour > nbMinutesLeft)
            {
                lastMinuteLeftOnLastHour -= nbMinutesLeft;
            }
        }

        lastIndexOnAveragesOn24HoursByHourNew = index;
    }
    else if (levels[indexLevel] == 4)
    {
        int durationInMinutes = (int)minutes[indexLevel];

        // A duration of zero means less than one minute
        // set it to one minute
        if (durationInMinutes == 0)
        {
            durationInMinutes = 1;
        }
        else if (durationInMinutes == 1439)
        {
            // FIXME Add Missing minute if needed
            durationInMinutes = 1440;
        }

        var nbMinutesLeft = durationInMinutes % 60;

        var nbHoursAtOneHundred = durationInMinutes / 60;
        int index = 0;
        for (index = lastIndexOnAveragesOn24HoursByHourNew; index < nbHoursAtOneHundred + lastIndexOnAveragesOn24HoursByHourNew; index++)
        {
#if DEBUG
            Debug.Assert(index <= 23);
#endif
            if (lastMinuteLeftOnLastHour != 0)
            {
                averagesOn24HoursByHourNew[index].level4 += lastMinuteLeftOnLastHour * 100 / 60;
                lastMinuteLeftOnLastHour = 0;
            }
            else
            {
                averagesOn24HoursByHourNew[index].level4 += 100;
            }

            datiCountForOneHour[index] += 1;
        }
#if DEBUG
        // When th whole day is processed the index will be 24
        Debug.Assert(index < 25);
#endif
        if (nbMinutesLeft != 0)
        {
            averagesOn24HoursByHourNew[index].level4 += nbMinutesLeft * 100 / 60;
            if (lastMinuteLeftOnLastHour != 0 && lastMinuteLeftOnLastHour > nbMinutesLeft)
            {
                lastMinuteLeftOnLastHour -= nbMinutesLeft;
            }
        }

        lastIndexOnAveragesOn24HoursByHourNew = index;
    }
    else if (levels[indexLevel] == 5)
    {
        int durationInMinutes = (int)minutes[indexLevel];

        // A duration of zero means less than one minute
        // set it to one minute
        if (durationInMinutes == 0)
        {
            durationInMinutes = 1;
        }
        else if (durationInMinutes == 1439)
        {
            // FIXME Add Missing minute if needed
            durationInMinutes = 1440;
        }

        var nbMinutesLeft = durationInMinutes % 60;

        var nbHoursAtOneHundred = durationInMinutes / 60;
        int index = 0;
        for (index = lastIndexOnAveragesOn24HoursByHourNew; index < nbHoursAtOneHundred + lastIndexOnAveragesOn24HoursByHourNew; index++)
        {
#if DEBUG
            Debug.Assert(index <= 23);
#endif
            if (lastMinuteLeftOnLastHour != 0)
            {
                averagesOn24HoursByHourNew[index].level5 += lastMinuteLeftOnLastHour * 100 / 60;
                lastMinuteLeftOnLastHour = 0;
            }
            else
            {
                averagesOn24HoursByHourNew[index].level5 += 100;
            }

            datiCountForOneHour[index] += 1;
        }
#if DEBUG
        // When th whole day is processed the index will be 24
        Debug.Assert(index < 25);
#endif
        if (nbMinutesLeft != 0)
        {
            averagesOn24HoursByHourNew[index].level5 += nbMinutesLeft * 100 / 60;
            if (lastMinuteLeftOnLastHour != 0 && lastMinuteLeftOnLastHour > nbMinutesLeft)
            {
                lastMinuteLeftOnLastHour -= nbMinutesLeft;
            }
        }

        lastIndexOnAveragesOn24HoursByHourNew = index;
    }
    else
    {
        throw new InvalidOperationException("Invalid protection level found in stats");
    }
}

Upvotes: 0

Views: 112

Answers (2)

derpirscher
derpirscher

Reputation: 17436

You can try it this way. Probably not the most elegant solution, but gets the job done and can be a basis for further optimization. For explanation see comments in the code.

using System;
using System.Linq;
                    
public class Program
{
    class percentages {
        public double l0;
        public double l1;
        public double l2;
        public double l3;
        public double l4;
        public double l5;
        
        public string ToString() {
          return $"{l0:F2} {l1:F2} {l2:F2} {l3:F2} {l4:F2} {l5:F2} ";
        }
    }
    
    public static void Main()
    {
        var levels = new int[]{0,5,0};
        var minutes = new int[]{658, 1, 781};
        var plist = new percentages[24];
        
        //sum up the minutes to total duration on this day
        //ie minutes will become [658, 659, 1440]
        for (int i = 1; i < minutes.Length; i++) 
            minutes[i] += minutes[i-1];
        
        //lindex is the current index in the levels/minutes array
        var lindex = 0; 
        //pindex is the current index in the percentages array
        var pindex = 0;
        
        var p = new percentages();
        
        //a constant of how much 1 minute contributes in percent of an hour
        var m1 = 100.0 / 60;
        
        //check every minute of the day
        for (int m = 1; m <= 1440; m++) {
            //increment the respective level by 1 minute
            switch(levels[lindex]) {
                case 0: p.l0 += m1; break;
                case 1: p.l1 += m1; break;
                case 2: p.l2 += m1; break;
                case 3: p.l3 += m1; break;
                case 4: p.l4 += m1; break;
                case 5: p.l5 += m1; break;
            }
            
            //if the minutes reached a level change increment the index in the level/minute array
            if (m == minutes[lindex]) lindex++;
            
            //if the minutes reached a full hour, add the current percentages to the list, increment index and reset for the next hour
            if (m % 60 == 0){
                plist[pindex++] = p;
                
                Console.WriteLine(p.ToString());
                p = new percentages();
            }
        }       
    }
}

Be aware, there are no sanity checks whatsoever. This will break if levels and minutes don't have the same length or if the sum of minutes is not 1440. For further use, you should add these checks.

Upvotes: 1

sfx
sfx

Reputation: 103

I suggest you create another array level_at_min where the index is a minute in the day. level_at_min will be of size 1440 (I show an algorithm to do it later).

In your example you will have

level_at_min[0] = 0 
...
level_at_min[657] = 0 
level_at_min[658] = 5 
level_at_min[659] = 0 
...
level_at_min[1439] = 0 

If you have that array, you can create an array of lists (of length 60) levels_at_hour, that will give for your example

levels_at_hour[0] = [0,0, ..., 0]    
levels_at_hour[1] = [0,0, ..., 0]    
....
levels_at_hour[11] = [0,..., 5,0]    
...
levels_at_hour[23] = [0,0,...,0]  

With this you can compute the average protection level at each hour, or the percentage of each protection level at each hour, whatever you want.

Algorithm to create level_at_min: (n the length of array levels and array minutes)

minute_counter = 0
for each block in 0 to n-1
  for each minute in minute_counter to (minute_counter+minutes[block])
    level_at_min[min] = levels[block]
  minute_counter = minute_counter + minutes[block]

Algorithm to create levels_at_hour:

for each hour in 0 to 23:
  for each minute in i*60 to (i+1)*60  
    add level_at_min[minute] to the list levels_at_hour[hour] 

Upvotes: 0

Related Questions