functions C# Linq aggregate intermediate values




linq functions in c# (5)

What you need is a custom version of aggregate:

public static IEnumerable<R> AggregateSequence<A, R>(
  this IEnumerable<A> items,
  Func<A, R, R> aggregator,
  R initial)
{
  // Error cases go here.
  R result = initial;
  foreach(A item in items)
  {
    result = aggregator(item, result);
    yield return result;
  }
}

That is a general mechanism for solving your specific problem:

public static IEnumerable<int> MovingSum(this IEnumerable<int> items)
{
  return items.AggregateSequence( (item, sum) => item + sum, 0 );
}

And now you can solve your problem with

mySequence.MovingSum().Max();

Given an array of positive and negative numbers is there a Linq expression that can get intermediate values?

for example

var heights = new List<int>();    
var numbers = new [] { 5, 15, -5, -15 };    
var curHeight = 0;

foreach (var number in numbers)
{
    curHeight = curHeight + number;
    heights.add(curHeight);
}

This function will return [5, 20, 15, 0]

Aggregate can be used the same way and it would go through this sequence

numbers.aggregate((a, b) => a + b);
0 + 5 = 5, 5 + 15 = 20, 20 - 5 = 15, 15 - 15 = 0

My question is, is there a way to use aggregate or some other so that the intermediate values [5, 20, 15, 0] are returned?


Solution with BAD perfomance:

static void Main(string[] args)
{
    var numbers = new[] { 5, 15, -5, -15 };

    var heights =  numbers.Select((o, i) => numbers.Take(i + 1).Sum()).ToList();
    foreach (var item in heights)
    {
        Console.WriteLine(item);
    }
}

It has O(n^2) complexity.


the one line version

var numbers = new[] { 5, 15, -5, -15 };
var curHeight = 0;

int best = numbers.Select(x => { curHeight += x; return curHeight; }).Max();

as Eric has pointed out this is correctly stated a bad practice even though it works here would be a non destructive way of doing it

var numbers = new[] { 5, 15, -5, -15 };

int best = Enumerable.Range(1, numbers.Length + 1).Select(x => numbers.Take(x).Sum()).Max();

You can write your own extension methods to do the accumulation/running totals with a custom projection and call it like:

static void Main(string[] args) {
      var numbers = new[] { 5, 15, -5, -15 };

      // results 5, 20, 15, 0
      var results = numbers.Accumulate((a, b) => a + b);
      var max = results.Max(); // 20

      // providing a initial seed value: 20, 35, 30, 15:  
      var result2 = numbers.Accumulate(15, (a, b) => a + b);
      var max2 = result2.Max(); // 35
}

The extension methods:

public static class AccumulateExt
{
    public static IEnumerable<TSource> Accumulate<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, TSource, TSource> funcAggregate)
    {
        return source.Accumulate(default(TSource), funcAggregate);
    }

    public static IEnumerable<TAggregate> Accumulate<TSource, TAggregate>(
        this IEnumerable<TSource> source,
        TAggregate initialValue,
        Func<TAggregate, TSource, TAggregate> funcAggregate)
    {

        return AccumulateImplementation(source, initialValue, funcAggregate, (_, agg) => agg);
    }

    public static IEnumerable<TResult> Accumulate<TSource, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, TSource, TSource> funcAggregate,
        Func<TSource, TSource, TResult> resultSelector)
    {

        return source.Accumulate(default(TSource), funcAggregate, resultSelector);

    }

    public static IEnumerable<TResult> Accumulate<TSource, TAggregate, TResult>(
        this IEnumerable<TSource> source,
        TAggregate initialValue,
        Func<TAggregate, TSource, TAggregate> funcAggregate,
        Func<TSource, TAggregate, TResult> resultSelector)
    {
        return AccumulateImplementation(source, initialValue, funcAggregate, resultSelector);
    }


    private static IEnumerable<TResult> AccumulateImplementation<TSource, TAggregate, TResult>(
        this IEnumerable<TSource> source,
        TAggregate initialValue,
        Func<TAggregate, TSource, TAggregate> funcAggregate,
        Func<TSource, TAggregate, TResult> resultSelector)
    {
        var last = initialValue;
        foreach (var item in source)
        {
            var value = funcAggregate(last, item);
            last = value;
            yield return resultSelector(item, value);
        }
    }
}

You can do this stuff within your aggregate function:

var intermidiateValues = new List<int>();
numbers.aggregate((intermidiateValue, next) => {
    intermidiateValues.Add(intermidiateValue);
    return intermidiateValue + next;
});

And then use

intermidiateValues.Max();




linq