Alex
Alex

Reputation: 2959

What is the fastest way to compare a value from a list to a specific sum from another list?

I have two huge lists of created objects. A List<Forecast> with all the forecasts from different Resources and a List<Capacity> with the capacities of these Resources.

A Forecast also contains booleans indicating if this Resource is over or below the capacity for the sum of all his forecasts.

public class Forecast
{
    public int ResourceId { get; set; }
    public double? ForecastJan { get; set; }
    // and ForecastFeb, ForecastMarch, ForecastApr, ForecastMay, etc.

    public bool IsOverForecastedJan { get; set; }
    // and IsOverForecastedFeb, IsOverForecastedMarch, IsOverForecastedApr, etc.
}

public class Capacity
{
    public int ResourceId { get; set; }
    public double? CapacityJan { get; set; }
    // and CapacityFeb, CapacityMar, CapacityApr, CapacityMay, etc.
}

I have to set the IsOverForecastXXX properties so I have to know for each month if the sum of forecasts for each resource is higher than the sum of the capacity for this specific resource.

Here is my code :

foreach (Forecast f in forecastList)
{
    if (capacityList.Where(c => c.Id == f.ResourceId)
                    .Select(c => c.CapacityJan)
                    .First()
        < forecastList.Where(x => x.ResourceId == f.ResourceId)
                      .Sum(x => x.ForecastJan)
    )
    {
        f.IsOverForecastedJan = true;
    }

    //Same for each month...
}

My code works but I have really bad performances when the lists are too big (thousands of elements).

Do you how of can I improve this algorithm ? How to compare the sum of forecasts for each resource with the associated capacity ?

Upvotes: 3

Views: 73

Answers (3)

Jon
Jon

Reputation: 437356

There are lots of things you can do to speed this up. First up, make a single pass over forecastList and sum the capacity forecast for each month:

var demandForecasts = new Dictionary<int, double?[]>();

foreach (var forecast in forecastList)
{
    var rid = forecast.ResourceId;
    if (!demandForecasts.ContainsKey(rid))
    {
        demandForecasts[rid] = new double?[12];
    }

    var demandForecast = demandForecasts[rid];

    demandForecast[0] += forecast.ForecastJan;
    // etc
    demandForecast[11] += forecast.ForecastDec;
}

Do the same for capacities, resulting in a capacities dictionary. Then, one more loop over forecastList to set the "over forecasted" flags:

foreach (var forecast in forecastList)
{
    var rid = forecast.ResourceId;
    forecast.IsOverForecastedJan = capacities[rid][0] < demandForecast[rid][0];
    // ...
    forecast.IsOverForecastedDec = capacities[rid][11] < demandForecast[rid][11];
}

As is obvious from the twelve-fold code repetition implicit in this code, modelling capacities etc as separate properties for each month is probably not the best way of doing things -- using some kind of indexed collection would allow the repetition to be eliminated.

Upvotes: 0

Tim Schmelter
Tim Schmelter

Reputation: 460098

You can use First or FirstOrdefault to get the capacities for the currect resource, then compare them. I would use ToLookup which is similar to a Dictionary to get all forecasts for all resources.

ILookup<int, Forecast> forecastMonthGroups = forecastList
    .ToLookup(fc => fc.ResourceId);
foreach (Forecast f in forecastList)
{
    double? janSum = forecastMonthGroups[f.ResourceId].Sum(fc => fc.ForecastJan);
    double? febSum = forecastMonthGroups[f.ResourceId].Sum(fc => fc.ForecastFeb);
    var capacities = capacityList.First(c => c.ResourceId == f.ResourceId);
    bool overJan = capacities.CapacityJan < janSum;
    bool overFeb = capacities.CapacityFeb < febSum;
    // ...
    f.IsOverForecastedJan = overJan;
    f.IsOverForecastedFeb = overFeb;
    // ...
}

It seems that there is only one Capacity per ResourceID, then i would use a Dictionary to store the "way" from ResourceId to Capacity, this would improve performance even more:

ILookup<int, Forecast> forecastMonthGroups = forecastList
    .ToLookup(fc => fc.ResourceId);
Dictionary<int, Capacity> capacityResources = capacityList
    .ToDictionary(c => c.ResourceId);
foreach (Forecast f in forecastList)
{
    double? janSum = forecastMonthGroups[f.ResourceId].Sum(fc => fc.ForecastJan);
    double? febSum = forecastMonthGroups[f.ResourceId].Sum(fc => fc.ForecastFeb);
    bool overJan = capacityResources[f.ResourceId].CapacityJan < janSum;
    bool overFeb = capacityResources[f.ResourceId].CapacityFeb < febSum;
    // ...
    f.IsOverForecastedJan = overJan;
    f.IsOverForecastedFeb = overFeb;
    // ...
}

Upvotes: 1

Mike Norgate
Mike Norgate

Reputation: 2431

I would try selecting out your capacities and forecasts for each month before entering the loop this way you are not iterating each list every time you go round the loop.

Something like this:

 var capicities = capacityList.GroupBy(c => c.ResourceId).ToDictionary(c=>c.First().ResourceId, c=>c.First().CapacityJan);
 var forecasts = forecastList.GroupBy(x => x.ResourceId).ToDictionary(x => x.First().ResourceId, x => x.Sum(f => f.ForecastJan));
 foreach (Forecast f in forecastList)
 {
     if (capicities[f.ResourceId] < forecasts[f.ResourceId])
     {
         f.IsOverForecastedJan = true;
     }

 }

Upvotes: 1

Related Questions