【问题标题】:How to calculate average of some timespans in C#如何计算C#中某些时间跨度的平均值
【发布时间】:2017-11-10 16:55:34
【问题描述】:

我已经搜索过这个问题,但很遗憾找不到正确的答案。

我想计算在 10 个不同的日子里花在做某事上的平均时间,我有 10 个datetimepicker 用于开始时间,还有 10 个datetimepicker 用于每天的结束时间(总共 20 个datetimepicker)。

现在我想获得这 10 天平均花费在工作上的时间。

这是我每天计算timespan所做的,现在我不知道如何计算这些timespans的平均值

当然我想知道有没有更短的方法来完成这项工作?

DateTime Dt1 = dateTimePicker1.Value;
DateTime Dt2 = dateTimePicker2.Value;
.
.
.
DateTime Dt20 = dateTimePicker20.Value;

TimeSpan Day1 = DateTime.Parse(Dt11.TimeOfDay.ToString()).Subtract(DateTime.Parse(Dt1.TimeOfDay.ToString()));
TimeSpan Day2 = DateTime.Parse(Dt12.TimeOfDay.ToString()).Subtract(DateTime.Parse(Dt2.TimeOfDay.ToString()));
.
.
.

TimeSpan Day10 = DateTime.Parse(Dt20.TimeOfDay.ToString()).Subtract(DateTime.Parse(Dt10.TimeOfDay.ToString()));

我想求 Day1 到 Day10 的平均值

【问题讨论】:

  • 如果你有一个数组或一个列表而不是 20 个单独的变量,那会容易得多。 (不清楚为什么要格式化 TimeSpan 值然后重新解析它们。)
  • 将它们全部相加并除以数字。正如 skeet 所说 - 把它们放在一个数组中
  • 我很好奇。为什么你决定创建一个字符串只是为了再次解析它?我对你的动机非常感兴趣,因为我一直都在看到这种方法,而且它从来都不是必需的,也不是可取的。
  • 另外,您是否保证所有值对都在同一日期?或者你能有一对跨越午夜的,比如晚上 11 点到凌晨 1 点之间的两个小时?
  • 如果所有的 DateTime 都有相同的 Date,为什么要把它去掉?无论如何,您只对差异感兴趣。所以var MyTimeSpan = myDateTime1 - myDateTime2; 似乎在没有TimeOfDayToString() 的情况下可以解决问题(最后一个确实非常无用,但由于某种原因非常常见)。

标签: c# datetimepicker timespan


【解决方案1】:

给定一个IEnumerable<TimeSpan>,您可以使用此扩展方法对它们进行平均:

public static TimeSpan Average(this IEnumerable<TimeSpan> spans) => TimeSpan.FromSeconds(spans.Select(s => s.TotalSeconds).Average());

因此,将您的结果转换为List

var durs = new List<TimeSpan>();
durs.Add(Dt11.TimeOfDay.Subtract(Dt1.TimeOfDay));
durs.Add(Dt12.TimeOfDay.Subtract(Dt2.TimeOfDay));
.
.
.

durs.Add(Dt20.TimeOfDay.Subtract(Dt10.TimeOfDay));

现在计算平均值:

var avgDurs = durs.Average();

PS:创建了@MattJohnson 答案的Aggregate 版本:

public static TimeSpan Mean(this IEnumerable<TimeSpan> source) => TimeSpan.FromTicks(source.Aggregate((m: 0L, r: 0L, n: source.Count()), (tm, s) => {
        var r = tm.r + s.Ticks % tm.n;
        return (tm.m + s.Ticks / tm.n + r / tm.n, r % tm.n, tm.n);
    }).m);

【讨论】:

  • 最好使用刻度而不是秒,以免丢失任何更精细的信息。尽管在 OP 的情况下可能无关紧要,但来自选择器控件,如果要保留扩展方法,则希望它可用于各种场景。
  • @MattJohnson 在溢出时添加刻度可能会成为问题。那么秒是一个更好的选择。
  • 谢谢,但我得到了这个错误:'List&lt;TimeSpan&gt;' does not contain a definition for 'Average' and the best extension method overload 'Queryable.Average(IQueryable&lt;int&gt;)' requires a receiver of type 'IQueryable&lt;int&gt;
  • 添加这个using System.Linq;
  • @PatrickHofman - 是的,但有other averaging methods 可以用来克服这个问题。尽管在这种特殊情况下我同意你的看法。 :)
【解决方案2】:

您可以通过根据刻度数实例化新的 TimeSpan 来找到平均值。

    var time_spans = new List<TimeSpan>() { new TimeSpan(24, 10, 0), new TimeSpan(12, 0, 45), new TimeSpan(23, 30, 0), new TimeSpan(11, 34, 0) };

    var average = new TimeSpan(Convert.ToInt64(time_spans.Average(t => t.Ticks)));

     Console.WriteLine(average); 

结果17:48:41.2500000

【讨论】:

    【解决方案3】:

    只是为了跟进 NetMage 的完美答案,请注意他的 Average 扩展方法正在使用 .TotalSeconds,它返回整数秒和小数秒的 double,精度为毫秒。如果您从时间选择器中获取值,这可能没问题,但在 general 情况下,它会导致精度损失很小。

    此外,TimeSpan.FromSeconds 方法在处理大值时可能会溢出。即使没有平均,也可以重现:

    TimeSpan.FromSeconds(TimeSpan.MaxValue.TotalSeconds)  // will throw an OverflowException
    

    Patrick Hofman 在 cmets 中提出了一个很好的观点,即从秒切换到滴答也可能导致溢出,因此需要另一种解决方案。

    采用this answer 中给出的技术,我们可以使用arithmetic mean 方法计算TimeSpan 值集合的平均值,而不会损失精度并且不会溢出:

    public static TimeSpan Mean(this ICollection<TimeSpan> source)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));
    
        long mean = 0L;
        long remainder = 0L;
        int n = source.Count;
        foreach (var item in source)
        {
            long ticks = item.Ticks;
            mean += ticks / n;
            remainder += ticks % n;
            mean += remainder / n;
            remainder %= n;
        }
    
        return TimeSpan.FromTicks(mean);
    }
    

    使用它与其他扩展方法类似,例如:

    TimeSpan average = mylistoftimespans.Mean();
    

    【讨论】:

    • 谢谢 我应该怎么做?如果它们是负数,我如何检查它们并将它们乘以 -1?
    • 处理负值不需要额外的代码。这里的所有内容都已签名。
    • 对于复杂的Aggregate 来说似乎是个好时机。顺便说一句,为什么我们是 ICollection 而不是 IEnumerable
    • 为了避免由于必须调用.Count()而可能出现的两个枚举
    • 我在IEnumerable 上创建了一个Aggregate 版本(并非所有IEnumerable 都实现ICollection),有趣的是Mean (5,10,15)秒返回一个稍差的结果Average,虽然超过 7 个TimeSpans 的样本,Mean 更准确。请注意,TimeSpan 秒精确到毫秒。
    猜你喜欢
    • 2019-10-27
    • 1970-01-01
    • 1970-01-01
    • 2016-03-20
    • 2015-07-14
    • 2016-10-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多