Search code examples
c#functionsumcombinations

Find the lowest possible value achievable from a pair of summation functions in C#


The aim of the code was to find the best possible combination of pit stops and tyre compounds for a 40-lap race. Any set of laps cannot be more than 25, as that is the point at which fuel runs out for the car modelled. The majority of the steps I wrote manually, as with one pitstop, there aren't too many possible combinations, but for a two pitstop (with 3 different sets of laps), it wouldn't make sense to write manually. Though at the same time, I can't figure out any way to do it outside of just writing it out...

Here is the full script so far (using decimal instead of double as it's for a math paper):

using System;
using System.Collections.Generic;
using System.Linq;
                    
public class Program
{
    public static void Main()
    {
        List<decimal> findMin = new List<decimal>();

        //Add all possible values to the list 
        for(int i = 25; i >= 20; i--)
        {
            findMin.Add(LowestCombo(i, 40 - i));
        }

        Console.WriteLine(" ");

        //Print lowest number in the list
        Console.WriteLine($"The lowest possible time achievable is {findMin.Min()}");

    }
    public static decimal LowestCombo (int a, int b){
        List<decimal> sumBoth = new List<decimal>();
        string[] identifier = {"all soft tyres", "all medium tyres", "soft, then medium", "medium, then soft"};

        //Add all possible combinations of the two to the list
        sumBoth.Add(SoftSummation(a) + SoftSummation(b));
        sumBoth.Add(MedSummation(a) + MedSummation(b));
        sumBoth.Add(SoftSummation(a) + MedSummation(b));
        sumBoth.Add(MedSummation(a) + SoftSummation(b));
        
        //Print the combination of laps and tyres as well as the time, then return the lowest time
        Console.WriteLine($"The lowest possible time achievable with {a} initial laps and {b} final laps for a 1 pit stop race is {sumBoth.Min()}, {identifier[sumBoth.IndexOf(sumBoth.Min())]}.");
        return sumBoth.Min();
    }

    
    public static decimal SoftSummation(int a)
    {   
        decimal sum = 0;
        
            for(int x = 1; x <= a; x++)
            {
                //Tyre degradation function for the soft compound (Math.Pow can't take decimal??)
                sum += (0.1262m * (x * x * x * x)) - (4.476m * (x * x * x)) + (56.37m * (x * x)) - (152.9m * x) + (1.427m * (100000m));
            }
        
        return sum; 
    }

    
    public static decimal MedSummation(int a)
    {   
        decimal sum = 0;
        
            for(int x = 1; x <= a; x++)
            {
                //Tyre degradation function for the medium compound
                sum += (0.8406m * (x * x)) + (44.77m * x) + (1.434m * 100000m);
            }
        
        return sum; 
    }
}

Apologies for the initially vague question, here's some clarification (still kind of struggling to explain it, but I hope it makes more sense than the original question):

The script is designed to find the lowest possible time achievable for a 40-lap race by checking different combinations of pit stops and tire compounds. What it does essentially divide the total race distance (40 laps) into two segments: an initial segment with a certain number of laps and a final segment with the remaining laps.

Neither of the segments can be more than 25 laps, so that only leaves with 20 different possible combinations (5 different possible combinations of segments, and 4 different combinations of tyre compounds). Because there aren't that many combinations, I've essentially just written out all the possible combinations of tyre compounds, and had a loop test all the possible combinations of segments.

Though, I'd also need to consider a two-pit stop strategy, which instead divides the total race into 3 segments, making just "writing all the combinations" out not very feasible. I wanted to know if there was a better way of writing it, maybe even automating the entire process.


Solution

  • The base problem you are trying to solve is a generic way of getting valid combinations of items per segment for an arbitrary number of segments with limitations on the total count and max items per segment. I thought this was an interesting problem to try to solve, and I found something that works, but it is a complicated solution.

    using System;
    using System.Collections.Generic;
    using System.Linq;
                        
    public class Program
    {
        public static void Main()
        {
            var segments = 3;
            //You don't specify a minimum lap count, but it may make sense
            //to have one. If you want the minimum to be 1 you can just
            //change that here.
            var minLapsPerSegment = 5;
            var maxLapsPerSegment = 25;
            var totalLaps = 40;
            var lapsPerSegment = new int[segments];
            //all combinations already tried. Using HashSet to optimize for "IsSubset" calls
            var combinations = new List<HashSet<int>>();
            //initialize all laps to minLapsPerSegment
            for(var i = 0; i < lapsPerSegment.Length; i++)
            {
                lapsPerSegment[i] = minLapsPerSegment;
            }
            //we will methodically increment lap counts to get all combinations
            //of valid laps per segment, but once the first lap count is over the
            //max lap count, that's where it ends.
            while(lapsPerSegment[0] <= maxLapsPerSegment)
            {
                //each pass we will increment through all possible lap counts
                //for the second to last segment, and then figure out what the last
                //segment lap count has to be for that count combination
                //after looping through all possibilities for the second to last segment
                //we will bubble up through the previous segments incrementing them so that
                //we get all combinations
                for(var laps = minLapsPerSegment; laps <= maxLapsPerSegment; laps++)
                {
                    lapsPerSegment[segments - 2] = laps;
                    var lastSegmentLapCount = getLastSegmentLapCount(lapsPerSegment, totalLaps);
                    //if the beginning laps already add up to the total laps such that
                    //the last segment lap count has to be less than the minimum, break now
                    //because the last segment will only get smaller
                    if (lastSegmentLapCount < minLapsPerSegment)
                    {
                        break;
                    }
                    //this is not a valid combination of laps, so continue onto the next
                    if (lastSegmentLapCount > maxLapsPerSegment)
                    {
                        continue;
                    }
                    lapsPerSegment[segments - 1] = lastSegmentLapCount;
                    var currentCombination = lapsPerSegment.ToHashSet();
                    //if the current combination of lap counts is a subset of any of the previous
                    //lap counts, then this is just a permutation of segments that have already been
                    //checked, so we can skip this combination.
                    if (combinations.Any(x => currentCombination.IsSubsetOf(x)))
                    {
                        continue;
                    }
                    //now we have a valid combination of laps per segment. This is where you would
                    //do any of your lap time calculations with the different tire types, but for
                    //this example I'm just going to print the valid combination of laps per segment
                    Console.WriteLine(String.Join(',',lapsPerSegment));
                    combinations.Add(lapsPerSegment.ToHashSet());
                }
                //edge case if we only have two segments, then after this first loop we are done
                //otherwise this would go into an infinite loop
                if (segments == 2)
                {
                    break;
                }
                var previousIndexToModify = segments - 3;
                while(previousIndexToModify >= 0)
                {
                    lapsPerSegment[previousIndexToModify]++;
                    //if the previous segment has maxed out, roll it back to min lap count and bubble up to the previous segment
                    //before that so that we increment through all the possible combinations
                    if(lapsPerSegment[previousIndexToModify] > maxLapsPerSegment && previousIndexToModify != 0)
                    {
                        lapsPerSegment[previousIndexToModify] = minLapsPerSegment;
                        previousIndexToModify--;
                    }
                    else
                    {
                        //we've incremented a previous segment, so time to loop again and test all combinations
                        //with these beginning lap segment counts.
                        break;
                    }
                }
            }
            Console.WriteLine(combinations.Count);
        }
        
        private static int getLastSegmentLapCount(int[] lapsPerSegment, int totalLaps)
        {
            var sumOfBeginningLaps = 0;
            for(var i = 0; i < lapsPerSegment.Length - 1; i++)
            {
                sumOfBeginningLaps += lapsPerSegment[i];
            }
            var lastSegmentLapCount = totalLaps - sumOfBeginningLaps;
            return lastSegmentLapCount;
        }
    }