从对象列表中选择基于时间的第一个、最后一个、最小值、最大值

本文关键字:第一个 最后一个 最大值 最小值 时间 列表 对象 选择 于时间 | 更新日期: 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 分钟的块找到以下内容:

  1. 最大价值
  2. 敏瓦尔
  3. openVal
  4. 关闭瓦尔

所以如果第一个日期时间值是 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 分钟间隔对列表进行分组。 这是一个有点快速和肮脏的例子(我已经更改了一些值以提供更好的测试用例,TimeDataMyObject相同(:

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

相关文章: