[英]Find min and max of cumulative sum in Linq
我有以下 function,我用它來查找終端累計正值和負值,它正在工作:
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來實現呢?
我可以看到 package MoreLinq ,但不確定從哪里開始!
是的,你可以像這樣使用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 擴展方法通過將 function 應用於輸入序列中的每個元素並使用前一個元素作為累加器來生成一個新序列。 在這種情況下,function 只是加法運算符,因此 Scan 方法生成一個輸入序列的累加和序列。
您可以嘗試標准的 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 ArgeumentNullValue(nameof(values));
(var min, var max, _) = values
.Aggregate((min : 0m, max : 0m, curr : 0m), (s, a) => (
Math.Min(s.min, s.curr + a),
Math.Max(s.max, s.curr + a),
s.curr + a));
return (min, max);
}
您提供的代碼的主要缺陷是,如果序列的運行總和始終保持在零以下或零以上,則算法錯誤地返回零作為終端之一。
拿着它:
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;
}
可以使用LINQ中的Aggregate方法來實現。 Aggregate 方法將 function 應用於序列中的每個元素並返回累積結果。 它以初始累加器 object 作為參數來跟蹤最小和最大的 function。
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);
});
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.