求Linq中累积和的最小值和最大值

v9tzhpje  于 2023-01-03  发布在  其他
关注(0)|答案(4)|浏览(157)

我使用以下函数来查找终端累计正负值,该函数正在工作:

public class CumulativeTotal
{
    [Test]
    public void CalculatesTerminalValue()
    {
        IEnumerable<decimal> sequence = new decimal[] { 10, 20, 20, -20, -50, 10 };

        var values = FindTerminalValues(sequence);
        Assert.That(values.Item1, Is.EqualTo(-20));
        Assert.That(values.Item2, Is.EqualTo(50));

        Assert.Pass();
    }

    public static Tuple<decimal,decimal> FindTerminalValues(IEnumerable<decimal> values)
    {
        decimal largest = 0;
        decimal smallest = 0;
        decimal current = 0;

        foreach (var value in values)
        {
            current += value;
            if (current > largest)
                largest = current;
            else if (current < smallest)
                smallest = current;
        }

        return new Tuple<decimal, decimal>(smallest,largest);
    }
}

然而,在学习的兴趣,我怎么能实现与Linq?
我可以看到一个包MoreLinq,但不确定从哪里开始!

6xfqseft

6xfqseft1#

您可以尝试标准Linq Aggregate方法:

// Let's return named tuple: unlike min, max 
// current .Item1 and .Item2 are not readable
public static (decimal min, decimal max) FindTerminalValues(IEnumerable<decimal> values) {
  //public method arguments validation
  if (values is null)
    throw new ArgumentNullException(nameof(values));

  (var min, var max, _) = values
    .Aggregate((min: decimal.MaxValue, max: decimal.MinValue, curr: 0m), 
               (s, a) => (Math.Min(s.min, s.curr + a), 
                          Math.Max(s.max, s.curr + a),
                          s.curr + a));

  return (min, max);
}
h7appiyu

h7appiyu2#

是的,你可以这样使用MoreLinq,它有Scan方法。

public static Tuple<decimal, decimal> FindTerminalValues(IEnumerable<decimal> values)
{
    var cumulativeSum = values.Scan((acc, x) => acc + x).ToList();

    decimal min = cumulativeSum.Min();
    decimal max = cumulativeSum.Max();

    return new Tuple<decimal, decimal>(min, max);
}

Scan扩展方法通过将函数应用于输入序列中的每个元素,并使用前一个元素作为累加器来生成新序列。在这种情况下,函数只是加法运算符,因此Scan方法生成输入序列的累加和序列。

umuewwlo

umuewwlo3#

你所展示的代码的主要缺陷是,如果序列的运行总和一直小于零或大于零,那么算法会错误地将零作为一个终端返回。
拿着这个

IEnumerable<decimal> sequence = new decimal[] { 10, 20, };

当前算法返回(0, 30),而实际上它应该是(10, 30)
要更正此问题,必须从序列的第一个值开始,将其作为默认的最小值和最大值。
下面是一个实现:

public static (decimal min, decimal max) FindTerminalValues(IEnumerable<decimal> values)
{
    if (!values.Any())
        throw new System.ArgumentException("no values");
        
    decimal first = values.First();
    
    IEnumerable<decimal> scan = values.Scan((x, y) => x + y);

    return scan.Aggregate(
        (min: first, max: first),
        (a, x) =>
        (
            min: x < a.min ? x : a.min, 
            max: x > a.max ? x : a.max)
        );
}

它使用System.Interactive来获取Scan运算符(但您也可以使用MoreLinq
但是,这种方法的缺点是IEnumerable<decimal>不能保证每次都返回相同的值,您要么需要(1)传入decimal[]List<decimal>或其他总是返回相同序列的结构,要么(2)确保只迭代IEnumerable<decimal>一次。
下面是如何做(2):

public static (decimal min, decimal max) FindTerminalValues(IEnumerable<decimal> values)
{
    var e = values.GetEnumerator();
    if (!e.MoveNext())
        throw new System.ArgumentException("no values");

    var terminal = (min: e.Current, max: e.Current);
    decimal value = e.Current;

    while (e.MoveNext())
    {
        value += e.Current;
        terminal = (Math.Min(value, terminal.min), Math.Max(value, terminal.max));
    }

    return terminal;
}
lstz6jyr

lstz6jyr4#

您可以使用LINQ中的Aggregate方法来实现这一点。Aggregate方法将函数应用于序列中的每个元素并返回累加结果。它将初始累加器对象作为参数来跟踪最小和最大函数。

public static Tuple<decimal,decimal> FindTerminalValues(IEnumerable<decimal> values)
{
    return values.Aggregate(
        // Initial accumulator value:
        new Tuple<decimal, decimal>(0, 0),
        // Accumulation function:
        (acc, value) =>
        {
            // Add the current value to the accumulator:
            var current = acc.Item1 + value;
            // Update the smallest and largest accumulated values:
            var smallest = Math.Min(current, acc.Item1);
            var largest = Math.Max(current, acc.Item2);
            // Return the updated accumulator value:
            return new Tuple<decimal, decimal>(smallest, largest);
        });
}

相关问题