Nane
Nane

Reputation: 337

Accumulate values of a list

I have a list with that each object has two fields:

I have some values like this:

... and so.

I need to generate another list, same format, but accumulating the Estimated field, date by date. So the result must be:

Right now, I'm calculating it in a foreach statement

        for (int iI = 0; iI < SData.TotalDays; iI++)
        {
           DateTime oCurrent = SData.ProjectStart.AddDays(iI);
           oRet.Add(new GraphData(oCurrent, GetProperEstimation(oCurrent)));
        }

Then, I can execute a Linq Sum for all the dates prior or equal to the current date:

  private static double GetProperEstimation(DateTime pDate)
  {
     return Data.Where(x => x.Date.Date <= pDate.Date).Sum(x => x.Estimated);
  }

It works. But the problem is that is ABSLOUTELLY slow, taking more than 1 minute for a 271 element list.

Is there a better way to do this?

Thanks in advance.

Upvotes: 2

Views: 2085

Answers (4)

Derviş Kayımbaşıoğlu
Derviş Kayımbaşıoğlu

Reputation: 30565

You can try this. Simple yet effective.

var i = 0;

var result = myList.Select(x => new MyObject
{
     Date = x.Date, 
     Estimated = i = i + x.Estimated
}).ToList();

Edit : try in this way

.Select(x => new GraphData(x.Date, i = i + x.Estimated))

Upvotes: 1

M.kazem Akhgary
M.kazem Akhgary

Reputation: 19149

This is exactly job of MoreLinq.Scan

var newModels = list.Scan((x, y) => new MyModel(y.Date, x.Estimated + y.Estimated));

New models will have the values you want.


in (x, y), x is the previous item and y is the current item in the enumeration.


Why your query is slow?

because Where will iterate your collection from the beginning every time you call it. so number of operations grow exponentially 1 + 2 + 3 + ... + n = ((n^2)/2 + n/2).

Upvotes: 2

John Wu
John Wu

Reputation: 52250

You can write a simple LINQ-like extension method that accumulates values. This version is generalized to allow different input and output types:

static class ExtensionMethods
{
    public static IEnumerable<TOut> Accumulate<TIn, TOut>(this IEnumerable<TIn> source, Func<TIn,double> getFunction, Func<TIn,double,TOut> createFunction)
    {
        double accumulator = 0;

        foreach (var item in source)
        {
            accumulator += getFunction(item);
            yield return createFunction(item, accumulator);
        }
    }
}

Example usage:

public static void Main()
{
    var list = new List<Foo>
    {
        new Foo { Date = new DateTime(2018,1,1), Estimated = 1 },
        new Foo { Date = new DateTime(2018,1,2), Estimated = 2 },
        new Foo { Date = new DateTime(2018,1,3), Estimated = 3 },
        new Foo { Date = new DateTime(2018,1,4), Estimated = 4 },
        new Foo { Date = new DateTime(2018,1,5), Estimated = 5 }
    };
    var accumulatedList = list.Accumulate
    ( 
        (item)      => item.Estimated,                    //Given an item, get the value to be summed
        (item, sum) => new { Item = item, Sum = sum }     //Given an item and the sum, create an output element
    );
    foreach (var item in accumulatedList)
    {
        Console.WriteLine("{0:yyyy-MM-dd} {1}", item.Item.Date, item.Sum);
    }

}

Output:

2018-01-01 1
2018-01-02 3
2018-01-03 6
2018-01-04 10
2018-01-05 15

This approach will only require one iteration over the set so should perform much better than a series of sums.

Link to DotNetFiddle example

Upvotes: 2

ruirodrigues1971__
ruirodrigues1971__

Reputation: 342

I will assume that what you said is real what you need hehehe

Algorithm

Create a list or array of values based in the original values ordered date asc
sumValues=0;
foreach (var x in collection){
  sumValues+= x.Estimated; //this will accumulate all the past values and present value
  oRet.Add(x.date, sumValues);
}

The first step (order the values) is the most important. For each will be very fast. see sort

Upvotes: 1

Related Questions