您不需要将所有delta 值存储在List<TimeSpan> 中以调用Average();只保留一个运行总和然后将其除以比较的对数 (timestamps.Length - 1) 会更有效。所以这...
// ...
List<TimeSpan> deltas = new();
for (int i = timestamps.Length - 1; i > 0; i--)
{
// ...
deltas.Add(delta);
// ...
}
var result = new {
// ...
Avg = TimeSpan.FromMilliseconds(deltas.Average(d => d.TotalMilliseconds))
};
……会改成……
// ...
TimeSpan sum = TimeSpan.Zero;
for (int i = timestamps.Length - 1; i > 0; i--)
{
// ...
sum += delta;
// ...
}
var result = new {
// ...
//TODO: Avoid division for sequences with less than 2 elements, if expected
Avg = TimeSpan.FromMilliseconds(sum.TotalMilliseconds / (timestamps.Length - 1))
};
Aggregate() 用于在序列过程中累积一个或多个值。这是一种使用Aggregate() 计算与for 循环相同的值的方法...
static (TimeSpan? Minimum, TimeSpan? Maximum, TimeSpan? Average, int Count) GetDeltaStatistics(IEnumerable<DateTimeOffset> timestamps)
{
var seed = (
Previous: (DateTimeOffset?) null,
Minimum: (TimeSpan?) null,
Maximum: (TimeSpan?) null,
Sum: TimeSpan.Zero,
Count: 0
);
return timestamps.Aggregate(
seed,
(accumulator, current) => {
if (accumulator.Previous != null)
{
TimeSpan delta = current - accumulator.Previous.Value;
if (++accumulator.Count > 1)
{
// This is not the first comparison; Minimum and Maximum are non-null
accumulator.Minimum = delta < accumulator.Minimum.Value ? delta : accumulator.Minimum.Value;
accumulator.Maximum = delta > accumulator.Maximum.Value ? delta : accumulator.Maximum.Value;
}
else
{
// No prior comparisons have been performed
// Minimum and Maximum must be null so unconditionally overwrite them
accumulator.Minimum = accumulator.Maximum = delta;
}
accumulator.Sum += delta;
Console.WriteLine($"{current:yyyy-MM-dd HH:mm:ss}->{accumulator.Previous:yyyy-MM-dd HH:mm:ss} | {delta}");
}
accumulator.Previous = current;
return accumulator;
},
accumulator => (
accumulator.Minimum,
accumulator.Maximum,
Average: accumulator.Count > 0
? new TimeSpan(accumulator.Sum.Ticks / accumulator.Count)
: (TimeSpan?) null,
accumulator.Count
)
);
}
Aggregate() 重载的第二个参数是 Func<>,它传递序列中的当前元素 (current) 和从上一次调用 Func<> (accumulator) 返回的状态。第一个参数提供accumulator 的初始值。第三个参数是一个Func<>,它将这个状态的最终值转换为Aggregate()的返回值。状态和返回值都是value tuples。
注意GetDeltaStatistics() 只需要IEnumerable<DateTimeOffset> 而不是IList<DateTimeOffset> 或DateTimeOffset[];但是,由于没有对相邻元素的随机访问,current 的值通过accumulator.Previous 传递到下一次调用。我还让调用者负责提供排序的输入,但您也可以在方法内轻松地执行此操作。
打电话给GetDeltaStatistics()...
static void Main()
{
DateTimeOffset now = new DateTimeOffset(new DateTime(2022, 1, 1, 0, 0, 0, 0));
DateTimeOffset[] timestamps = new[] {
now,
now.AddSeconds(5),
now.AddSeconds(10),
now.AddSeconds(15),
now.AddSeconds(30),
now.AddSeconds(31)
};
IEnumerable<IEnumerable<DateTimeOffset>> timestampSequences = new IEnumerable<DateTimeOffset>[] {
timestamps,
timestamps.Take(2),
timestamps.Take(1),
timestamps.Take(0)
};
foreach (IEnumerable<DateTimeOffset> sequence in timestampSequences)
{
var (minimum, maximum, average, count) = GetDeltaStatistics(sequence.OrderBy(offset => offset));
Console.WriteLine($"Minimum: {GetDisplayText(minimum)}");
Console.WriteLine($"Maximum: {GetDisplayText(maximum)}");
Console.WriteLine($"Average: {GetDisplayText(average)}");
Console.WriteLine($" Count: {count}");
Console.WriteLine();
}
}
static string GetDisplayText(TimeSpan? delta) => delta == null ? "(null)" : delta.Value.ToString();
...产生这个输出...
2022-01-01 00:00:05->2022-01-01 00:00:00 | 00:00:05
2022-01-01 00:00:10->2022-01-01 00:00:05 | 00:00:05
2022-01-01 00:00:15->2022-01-01 00:00:10 | 00:00:05
2022-01-01 00:00:30->2022-01-01 00:00:15 | 00:00:15
2022-01-01 00:00:31->2022-01-01 00:00:30 | 00:00:01
最小值:00:00:01
最大值:00:00:15
平均:00:00:06.2000000
计数:5
2022-01-01 00:00:05->2022-01-01 00:00:00 | 00:00:05
最小值:00:00:05
最大值:00:00:05
平均:00:00:05
计数:1
最小值:(空)
最大值:(空)
平均:(空)
计数:0
最小值:(空)
最大值:(空)
平均:(空)
计数:0
虽然原始代码会导致抛出异常,但对于少于两个元素的序列,结果的 Count 为 0,其他字段为 null。