从对象列表中选择基于时间的第一个、最后一个、最小值、最大值
本文关键字:第一个 最后一个 最大值 最小值 时间 列表 对象 选择 于时间 | 更新日期: 2023-09-27 18:27:01
>我有一个MyObject列表,如下所示:
public MyObject(reqVal, reqTime)
{
_value = reqVal;
_time = reqTime;
}
public double Value
{
get {return _value;}
}
public DateTime Time
{
get {return _time;}
}
var myList = new List<MyObject>();
myList.Add(new MyObject(100, new DateTime(2012, 03, 01, 10, 0, 0));
myList.Add(new MyObject(50, new DateTime(2012, 03, 01, 10, 3, 0));
myList.Add(new MyObject(10, new DateTime(2012, 03, 01, 10, 6, 0));
myList.Add(new MyObject(230, new DateTime(2012, 03, 01, 10, 9, 0));
....
如您所见,此列表保存一整天的值,每个值每 3 分钟生成一次。我如何根据 15 分钟的块找到以下内容:
- 最大价值
- 敏瓦尔
- openVal
- 关闭瓦尔
所以如果第一个日期时间值是 2012/03/01 10:00我需要在 10:00 到 10:15 之间找到上面的 4 个,然后在 10:15 到 10:30 之间找到下一组 4 个,依此类推......
因此,所有这些值都将根据其时间范围进行计算例如,第二个maxVal或openVal将是10:15和10:30之间的maxVal和openVal
任何帮助将不胜感激,
谢谢。
我可能会使用这样的东西:
var end = start + TimeSpan.FromMinutes(15);
// Avoid querying more than once.
var matches = input.Where(x => x.Time >= start && x.Time < end)
.ToList();
// TODO: Consider what you want to do if there are no matches.
// (The code below would fail.)
var max = matches.Max(x => x.Value);
var min = matches.Min(x => x.Value);
var open = matches.First().Value;
var close = matches.Last().Value;
使用 Aggregate
您可以在一次传递输入数据时完成所有这些操作,而无需创建列表......但它会更加复杂。保持简单,然后进行基准测试以查看此解决方案是否适合您。
与杰斐逊先生的回答非常相似,但返回日期时间作为分组的关键字段,因为我假设您可能希望绘制这些结果。
List<MyObject> inputList = new List<MyObject>();
var resultSet = inputList
.GroupBy(i => i.GetStartOfPeriodByMins(15))
.Select( gr =>
new {
StartOfPeriod = gr.Key,
Min = gr.Min(item => item.Value),
Max = gr.Max(item => item.Value),
Open = gr.OrderBy(item => item.Time).First().Value,
Close = gr.OrderBy(item => item.Time).Last().Value
});
基于MyObject的这个定义,在对象本身上实现了GetStartOfPeriodByMins,尽管你可以把它放在任何地方:
public class MyObject
{
public double Value { get; set; }
public DateTime Time { get; set; }
public DateTime GetStartOfPeriodByMins(int numMinutes)
{
int oldMinutes = Time.Minute;
int newMinutes = (oldMinutes / numMinutes) * numMinutes;
DateTime startOfPeriod = new DateTime(Time.Year, Time.Month, Time.Day, Time.Hour, newMinutes, 0);
return startOfPeriod;
}
}
您可以尝试按 15 分钟间隔对列表进行分组。 这是一个有点快速和肮脏的例子(我已经更改了一些值以提供更好的测试用例,TimeData
与MyObject
相同(:
List<TimeData> myList = new List<TimeData>();
myList.Add(new TimeData(100, new DateTime(2012, 03, 01, 10, 0, 0)));
myList.Add(new TimeData(50, new DateTime(2012, 03, 01, 10, 3, 0)));
myList.Add(new TimeData(10, new DateTime(2012, 03, 01, 10, 35, 0)));
myList.Add(new TimeData(230, new DateTime(2012, 03, 01, 10, 46, 0)));
var grouped = myList.GroupBy(t => t.Time.Day.ToString()
+ "_" + t.Time.Month.ToString()
+ "_" + t.Time.Year.ToString()
+ "_" + t.Time.Hour.ToString() + "_"
+ (t.Time.Minute / 15).ToString())
.Select(gr => new { TimeSlot = gr.Key, Max = gr.Max(item => item.Value),
Min = gr.Min(item => item.Value),
Open = gr.OrderBy(g => g.Time).First().Value,
Close = gr.OrderBy(g => g.Time).Last().Value });
所以正在发生的事情是:
- 按天 + 月 + 年 + 小时 + (分钟/15( 分组。
(t.Time.Minute / 15)
是整数除法,这意味着从 0 到 14 的任何分钟值都等于 0,15 到 29 将等于 1,依此类推。
将每个组 - 转换为一个新类(为了空间起见,我将其设置为匿名(,每个组的键(在 GroupBy 中生成的字符串(以及组中的最大值、最小值、第一个值和最后一个值。
您可以通过稍微不同地执行GroupBy
来使运行速度更快(即使用类作为组键而不是为每个类构建一个字符串(。
更新:我为此生成了一个更好的测试用例,方法是向myList
添加 10,000 个元素,并随机输入值和分钟值。 我在生成grouped
结束时添加了一个.ToList()
,以确保懒惰评估不是一个因素。 它在 34 毫秒内运行。 我尝试了 100,000 个元素,它在 226 毫秒内运行(以 StopWatch
为单位(。 看起来性能不是一个大问题,除非资源受到限制并且您有数十万个元素myList
。