С# Linq агрегирует промежуточные значения

Учитывая массив положительных и отрицательных чисел, есть выражение Linq, которое может получать промежуточные значения или максимальное значение?

например

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);
}

Эта функция вернет [5, 20, 15, 0]

Агрегат можно использовать одинаково, и он будет проходить через эту последовательность

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

Мой вопрос в том, есть ли способ использовать агрегат или какой-либо другой, чтобы возвращались промежуточные значения [5, 20, 15, 0]?

В качестве альтернативы можно ли отслеживать максимальное значение? Мне действительно нужно вернуть значение 20.

Ответ 1

Вам нужна настраиваемая версия агрегата:

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;
  }
}

Это общий механизм для решения вашей конкретной проблемы:

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

И теперь вы можете решить свою проблему с помощью

mySequence.MovingSum().Max();

Ответ 2

С использованием Aggregate:

var result = numbers.Aggregate(new List<int>(), (a,b)=>
{
    a.Add(a.LastOrDefault()+b);
    return a;
});

результат wil be:

[5,20,15,0]

Если вы хотите иметь значение Max, вы можете использовать собственный класс результата:

public class Result
{
    public List<int> Values {get;set;}
    public int Max => Values.Max();
    public Result()
    {
        Values = new List<int>();
    }
}

используйте его:

var result = numbers.Aggregate(new Result(), (a,b)=>
{
    a.Values.Add(a.Values.LastOrDefault()+b);       
    return a;
});

и вы получите тот же список, что и result.Values и [20], как result.Max

Ответ 3

Вы можете сделать это в своей совокупной функции:

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

И затем используйте

intermidiateValues.Max();

Ответ 4

Решение с BAD-производительностью:

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);
    }
}

Он имеет сложность O (n ^ 2).

Ответ 5

Вы можете написать свои собственные методы расширения для выполнения итоговых сумм накопления/выполнения с помощью настраиваемой проекции и называть его следующим:

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
}

Методы расширения:

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);
        }
    }
}

Ответ 6

версия с одной строкой

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

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

как отметил Эрик, это правильно сказано, что плохая практика, хотя она работает здесь, будет не разрушительным способом ее выполнения.

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

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