如何在 DateTime 值数组中查找平均日期/时间

Posted

技术标签:

【中文标题】如何在 DateTime 值数组中查找平均日期/时间【英文标题】:How to find an average date/time in the array of DateTime values 【发布时间】:2013-05-17 00:16:49 【问题描述】:

如果我有一组 DateTime 值:

List<DateTime> arrayDateTimes;

有什么方法可以求出其中的平均DateTime?

例如,如果我有:

2003-May-21 15:00:00
2003-May-21 19:00:00
2003-May-21 20:00:00

平均值应该是:

2003-May-21 18:00:00

【问题讨论】:

+1 好问题。看到这个42zone.blogspot.com/2011/09/…,刚刚对其进行了测试,可以使用超过 38,000 个日期。 请注意,有些答案会保留时区信息,而另一些则不会.. 【参考方案1】:

如果你有很大的列表,你可以使用下面的方法

var count = dates.Count;
double temp = 0D;
for (int i = 0; i < count; i++)

    temp += dates[i].Ticks / (double)count;

var average = new DateTime((long)temp);

【讨论】:

大列表会抛出溢出异常。 dates[i].Ticks / count 如果 count > Ticks 将返回 0 Console.WriteLine(ticks / (ticks + 1)); Console.WriteLine(ticks / long.MaxValue);会打印什么? @Uzzy:滴答数以自 1601 年 1 月 1 日以来经过的 100 纳秒间隔的数量来衡量。我不知道这个数字的词,但这可能看起来像 635,047,830,427,420,548所以我认为count 不会大于Ticks @Damith: System.Int64 一个整数。【参考方案2】:

这不应该溢出,但它确实假设日期时间是有序的:

var first = dates.First().Ticks;
var average = new DateTime(first + (long) dates.Average(d => d.Ticks - first));

上面确实溢出了更大的列表和更大的间隙。我认为您可以使用秒来获得更好的范围。 (再次,先排序)另外,这可能不是最高效的方法,但对我来说仍然相对较快地完成了 1000 万个日期。不知道它是否更容易阅读,YYMV。

var first = dates.First();
var average = first.AddSeconds(dates.Average(d => (d - first).TotalSeconds));

【讨论】:

我不确定我是否遵循。 Ticks 是 long 类型。未来的滴答,减去过去的滴答,将给出一个相对较小的数字,并且不应该有溢出的可能性。 @c00000fd 没问题,很高兴为您提供解决方案。 这会再次导致溢出,日期列表很大,使用列表中的4704 日期进行测试。 经过测试,这给出了Arithmetic operation resulted in an overflow. 它确实溢出了相当大的列表,其中有相当大的差距。我已经用秒更新了答案。【参考方案3】:

代码:

var count = dates.Count;
double temp = 0D;
for (int i = 0; i < count; i++)

    temp += dates[i].Ticks / (double)count;

var average = new DateTime((long)temp);

错了。平均值=(x1 + x2 + ... xN) / N 不是 (x1/N + x2/N + ... xN/N)

试试:

var avg=new DateTime((long)dates.Select(d => d.Ticks).Average());

【讨论】:

如果对一个大数求平均值,因为总和(除以 N 之前)可能超过 long.MaxValue,这可能会导致算术溢出。替代方法更稳健。 其实(x1 + x2 + ... xN) / N(x1/N + x2/N + ... xN/N)是相等的。这只是一种不同的写作方式。 (但正如@BrendanHill 所写,第二种方法更健壮)【参考方案4】:

来源:取自Here并稍作修改。

List<DateTime> dates = new List<DateTime>();
//Add dates
for (int i = 1; i <= 28; i++) //days
    for (int j = 1; j <= 12; j++) //month
        for (int k = 1900; k <= 2013; k++) //year
            dates.Add(new DateTime(k, j, i, 1, 2, 3)); //over 38000 dates

那么你可以这样做:

var averageDateTime = DateTime
                            .MinValue
                            .AddSeconds
                            ((dates
                                 .Sum(r => (r - DateTime.MinValue).TotalSeconds))
                                     / dates.Count);
Console.WriteLine(averageDateTime.ToString("yyyy-MMM-dd HH:mm:ss"));

输出:1956-Dec-29 06:09:25

原来文章中的代码是这样的:

double totalSec = 0;
for (int i = 0; i < dates.Count; i++)

    TimeSpan ts = dates[i].Subtract(DateTime.MinValue);
    totalSec += ts.TotalSeconds;

double averageSec = totalSec / dates.Count;
DateTime averageDateTime = DateTime.MinValue.AddSeconds(averageSec);

【讨论】:

【参考方案5】:
class Program

    static void Main(string[] args)
    
        List<DateTime> dates = new List<DateTime>()
        new DateTime(2003, 5, 21, 16, 0, 0), new DateTime(2003, 5, 21, 17, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 17, 0, 0), new DateTime(2003, 5, 21, 18, 0, 0),
        new DateTime(2003, 5, 21, 19, 0, 0), new DateTime(2003, 5, 21, 20, 0, 0),
        new DateTime(2003, 5, 21, 16, 0, 0), new DateTime(2003, 5, 21, 17, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
        new DateTime(2003, 5, 21, 18, 0, 0), new DateTime(2003, 5, 21, 19, 0, 0),
        new DateTime(2003, 5, 21, 20, 0, 0), new DateTime(2003, 5, 21, 16, 0, 0),
    ;

        var averageDate = dates.Average();

        Console.WriteLine(averageDate);

        Console.ReadKey();
    



public static class Extensions

    public static long Average(this IEnumerable<long> longs)
    
        long count = longs.Count();

        long mean = 0;

        foreach (var val in longs)
        
            mean += val / count;
        

        return mean;
    

    public static DateTime Average(this IEnumerable<DateTime> dates)
    
        return new DateTime(dates.Select(x => x.Ticks).Average());
    

【讨论】:

我显然不是用 3 个值来做的 :) 你的方法会溢出大约 20 个日期。 @c00000fd: 40 个日期时间和一个扩展方法之后,不再溢出。 是的,谢谢。尽管上面的@Damith 已经提出了建议。【参考方案6】:

使用双秒而不是长滴答声将避免任何实际输入溢出 - 此处的扩展方法。

    public static DateTime Average(this IEnumerable<DateTime> elements)
    
        if (elements == null)
        
            throw new ArgumentNullException(nameof(elements));
        
        var enumerated = elements.ToArray(); //so we don't iterate a potentially one-use stream multiple times.
        if (!enumerated.Any())
        
            throw new ArgumentException("Average of 0 elements is undefined", nameof(elements));
        

        var epoch = enumerated.Min();
        var secondsSinceEpoch = enumerated.Select(d => (d - epoch).TotalSeconds).ToArray();
        var n = secondsSinceEpoch.LongLength;
        double totalSecondsSinceEpoch = secondsSinceEpoch.Sum();
        return epoch.AddSeconds(totalSecondsSinceEpoch / n);
    

    [TestMethod]
    public void HugeDateAverage_DoesntThrow()
    
        var epoch = new DateTime(1900,1,1);
        try
        
            var dates = Enumerable.Range(1, 1_000_000_000)
             .Select(i => epoch.AddSeconds(i));
            var result = dates.Average();
        
        catch (Exception ex)
        
            Assert.Fail();
        
    

如果你真的想退化,你可以检测到溢出并递归一半的元素,小心奇数 N 的情况。这是未经测试的,但想法如下:

    //NOT FOR ACTUAL USE - JUST FOR FUN
    public static DateTime AverageHuge(this IEnumerable<DateTime> elements)
    
        if (elements == null)
        
            throw new ArgumentNullException(nameof(elements));
        
        var enumerated = elements.ToArray(); //so we don't iterate a potentially one-use stream multiple times.
        if (!enumerated.Any())
        
            throw new ArgumentException("Average of 0 elements is undefined", nameof(elements));
        

        var epoch = enumerated.Min();
        var secondsSinceEpoch = enumerated.Select(d => (d - epoch).TotalSeconds).ToArray();
        var n = secondsSinceEpoch.LongLength;
        if (n > int.MaxValue)
        
            //we could actually support more by coding Take+Skip with long arguments.
            throw new NotSupportedException($"only int.MaxValue elements supported");
        

        try
        
            double totalSecondsSinceEpoch = secondsSinceEpoch.Sum(); //if this throws, we'll have to break the problem up
            //otherwise we're done.
            return epoch.AddSeconds(totalSecondsSinceEpoch / n);
        
        catch (OverflowException)   //fall out of this catch first so we don't throw from a catch block

        //Overengineering to support large lists whose totals would be too big for a double.
        //recursively get the average of each half of values.
        int pivot = (int)n / 2;
        var avgOfAvgs = (new []
        
            enumerated.Take(pivot).AverageHuge(),
            enumerated.Skip(pivot).Take(pivot).AverageHuge()
        ).AverageHuge();
        if (pivot * 2 == n)
           // we had an even number of elements so we're done.
            return avgOfAvgs;
        
        else
           //we had an odd number of elements and omitted the last one.
            //it affects the average by 1/Nth its difference from the average (could be negative)
            var adjust = ((enumerated.Last() - avgOfAvgs).TotalSeconds) / n;
            return avgOfAvgs.AddSeconds(adjust);
        
        
    

【讨论】:

【参考方案7】:

answer by neouser99 是正确的。它通过执行增量平均来防止溢出。

但是,这个answer by David Jiménez 是错误的,因为它没有处理溢出和他对公式的误解。

平均=(x1 + x2 + ... xN) / N 不是 (x1/N + x2/N + ... xN/N)

这些是相同的公式。这是使用分配属性的简单数学:

2(x + y) = 2x + 2y

平均公式与将总和乘以 1/N 相同。或者将每个单独的 X 乘以 1/N 并将它们相加。

1/n (x1 + x2 + ... xn)

由分配属性变为:

x1/n + x2/n + ... xn/n

这里有一些info on the distributive property

他的答案也很糟糕,因为它不能像接受的答案那样防止溢出。

我会评论他的回复,但我没有足够的声誉。

【讨论】:

这没有提供问题的答案。一旦你有足够的reputation,你就可以comment on any post;相反,provide answers that don't require clarification from the asker。 - From Review 我添加了一个编辑:我澄清了为什么接受的答案是正确的,这并不能解释任何事情(因为它可以防止溢出)以及为什么另一个得分为正的答案是错误的。所以我相信这确实回答了最初的问题。

以上是关于如何在 DateTime 值数组中查找平均日期/时间的主要内容,如果未能解决你的问题,请参考以下文章

对于特定的日期时间,在 [关闭] 之前和之后查找日期时间最近的记录

从日期集合中查找平均日期(Ruby)

查找整数数组的平均值

计算python日期时间的平均值

ion-datetime:如何获取没有时间戳的日期值?

在 DateTime 对象 python 中查找日期的索引