根据对象列表中的时间选择第一个,最后一个,最小值,最大值

时间:2012-03-06 17:44:57

标签: c# .net linq

我有一个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. MAXVAL
  2. MINVAL
  3. openVal
  4. closeVal
  5. 所以如果第一个日期时间值是2012/03/01 10:00 我需要在10:00到10:15之间找到上面的4,然后在10:15到10:30之间找到下一个4,依此类推......

    所以所有这些值都将根据它们的时间范围计算出来 例如,第二个maxVal或openVal将是10:15到10:30之间的maxVal和openVal

    任何帮助将不胜感激,

    感谢。

3 个答案:

答案 0 :(得分:2)

我可能会使用这样的东西:

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可以在输入数据的一次传递中完成所有这些操作,而无需创建列表......但这会非常复杂。保持简单,然后进行基准测试,看看这个解决方案是否能为你做好

答案 1 :(得分:2)

非常类似于Jefferson先生的回复,但返回DateTime作为分组的关键字段,我认为您可能想要绘制这些结果。

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;
    }
}

答案 2 :(得分:1)

您可以尝试对列表进行分组,其间隔为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中生成的字符串)以及组中的max,min,first和last值。

你可以通过稍微改变GroupBy来使运行更快(即使用类作为组密钥而不是为每个组构建一​​个字符串)。

更新:我通过向myList添加10,000个元素以及值和分钟的随机值,为自己生成了一个更好的测试用例。我在.ToList()代的末尾添加了grouped,以确保延迟评估不是一个因素。它跑了34毫秒。我尝试了100,000个元素,它运行在226毫秒(由StopWatch测量)。看起来性能不是一个大问题,除非资源受到约束,并且myList中有数十万个元素。