如果我有一个包含 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
如果我有一个包含 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
如果您有大型列表,可以使用以下方法
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);
System.Int64
是一个整数。 - c00000fd这个不应该溢出,但是它假设日期时间是有序的:
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));
4704
个日期进行测试。 - Habib代码:
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());
(x1 + x2 + ... xN) / N
和 (x1/N + x2/N + ... xN/N)
是相等的。只是不同的写法而已。(但正如@BrendanHill所写,第二种方法更加健壮) - Mathias来源:取自这里并稍作修改。
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年12月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);
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());
}
}
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);
}
}
所有与long相关的解决方案都导致了溢出问题。
我建议:
public static DateTime DateTimeAverage(IEnumerable<DateTime> dates) => DateTime.FromOADate(dates.Select(d => d.ToOADate()).Average());
neouser99的回答是正确的。它通过进行递增平均值来防止溢出。
然而,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
这是分配率相关信息。
他的答案也不好,因为它不能像被接受的答案那样防止溢出。
我本来想评论他的回复,但是我声望不够。