如何从Dictionary中的值获取特定时间段的最大总和范围

时间:2014-01-03 21:49:10

标签: c# linq

如果下面的词典中有一系列带有日期时间的数字,那么以编程方式选择特定时间段的最大总和范围的最有效方法是什么?下面只是一个例子,但我的实际数据在字典中有超过10,000个条目。

12/31/2013 10:00:01  96
12/31/2013 10:00:02  20
12/31/2013 10:00:03  51
12/31/2013 10:00:04  62
12/31/2013 10:00:05  84
12/31/2013 10:00:06  78
12/31/2013 10:00:07  74
12/31/2013 10:00:08 150
12/31/2013 10:00:09 130
12/31/2013 10:00:10  99
12/31/2013 10:00:11 101
12/31/2013 10:00:12 123
12/31/2013 10:00:13  51
12/31/2013 10:00:14  61
12/31/2013 10:00:15  19
12/31/2013 10:00:16  81
12/31/2013 10:00:17  98
12/31/2013 10:00:18  39
12/31/2013 10:00:19  45
12/31/2013 10:00:20  65

例如,5秒的最大总和范围是10:00:08到10:00:12。 显然,有几种方法可以获得此范围,但我正在寻找最有效的方式来在 C#中执行此操作。 你能否分享一下你的想法和技巧(也许是Linq,但没有必要)? 感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

如果您正在寻找效率,LINQ不会帮助您。

我的脑子里有蛮力算法:

  1. 将字典转换为时间/值列表
  2. 按时间对列表进行排序
  3. 使用某种“移动窗口”技术,您可以在其中添加新条目的值并删除旧条目的值。窗口的大小取决于您指定的范围。
  4. 对于每个窗口,您将获得该窗口的总和,以便查找最大窗口。
  5. 这里的来源(我借用了解析StriplingWarrior中的代码):

        public struct TimeValue
        {
            public DateTime time {get;set;}
            public int value{get;set;}
        }
    
        static void Main(string[] args)
        {
            var txt = @"12/31/2013 10:00:01  96
            12/31/2013 10:00:02  20
            12/31/2013 10:00:03  51
            12/31/2013 10:00:04  62
            12/31/2013 10:00:05  84
            12/31/2013 10:00:06  78
            12/31/2013 10:00:07  74
            12/31/2013 10:00:08 150
            12/31/2013 10:00:09 130
            12/31/2013 10:00:10  99
            12/31/2013 10:00:11 101
            12/31/2013 10:00:12 123
            12/31/2013 10:00:13  51
            12/31/2013 10:00:14  61
            12/31/2013 10:00:15  19
            12/31/2013 10:00:16  81
            12/31/2013 10:00:17  98
            12/31/2013 10:00:18  39
            12/31/2013 10:00:19  45
            12/31/2013 10:00:20  65";
    
            var values = 
                (from line in txt.Split(new[]{Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)
                let pair = line.Split(new[]{' '}, 3, StringSplitOptions.RemoveEmptyEntries)
                select new TimeValue
                {
                    time = DateTime.ParseExact(pair[0] + " " + pair[1], "MM/dd/yyyy HH:mm:ss", CultureInfo.InvariantCulture),
                    value = int.Parse(pair[2])
                })
                .OrderBy(e => e.time)
                .ToArray();;
    
            TimeSpan range = TimeSpan.FromSeconds(5);
    
            int totalMax = -1;
            int maxTail = -1;
            int maxHead = -1;
    
            int tail = 0; // index at tail of the window
            int currentMax = 0;
            for (int head = 0; head < values.Length; head++) // head of the window
            {
                currentMax += values[head].value; // add next value
                DateTime tailTime = values[head].time - range;
                while (values[tail].time <= tailTime) // remove values at tail that don't fit in range
                {
                    currentMax -= values[tail].value;
                    tail++;
                }
    
                if (currentMax > totalMax)
                {
                    totalMax = currentMax;
                    maxTail = tail;
                    maxHead = head;
                }
            }
    
            Console.WriteLine("Maximum range from times:" + values[maxTail].time + " - " + values[maxHead].time);
        }
    

答案 1 :(得分:1)

此代码似乎在LINQPad中有效。它遵循@Euphoric建议的模式。

void Main()
{
    var txt = @"12/31/2013 10:00:01  96
    12/31/2013 10:00:02  20
    12/31/2013 10:00:03  51
    12/31/2013 10:00:04  62
    12/31/2013 10:00:05  84
    12/31/2013 10:00:06  78
    12/31/2013 10:00:07  74
    12/31/2013 10:00:08 150
    12/31/2013 10:00:09 130
    12/31/2013 10:00:10  99
    12/31/2013 10:00:11 101
    12/31/2013 10:00:12 123
    12/31/2013 10:00:13  51
    12/31/2013 10:00:14  61
    12/31/2013 10:00:15  19
    12/31/2013 10:00:16  81
    12/31/2013 10:00:17  98
    12/31/2013 10:00:18  39
    12/31/2013 10:00:19  45
    12/31/2013 10:00:20  65";
    var data = 
        (from line in txt.Split(new[]{Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)
        let pair = line.Split(new[]{' '}, 3, StringSplitOptions.RemoveEmptyEntries)
        select new Entry
        {
            dt = DateTime.Parse(pair[0] + " " + pair[1]),
            val = int.Parse(pair[2])
        })
        .OrderBy(e => e.dt);

    var range = TimeSpan.FromSeconds(5);
    var queue = new Queue<Entry>();
    int max = 0;
    DateTime? maxStart = null;
    DateTime? maxEnd = null;
    int sum = 0;

    foreach (var entry in data)
    {
        queue.Enqueue(entry);
        sum += entry.val;
        while(queue.Count > 0 && entry.dt - queue.Peek().dt >= range)
        {
            sum -= queue.Dequeue().val;
        }
        if(sum > max)
        {
            max = sum;
            maxStart = queue.Peek().dt;
            maxEnd = entry.dt;
        }
    }
    Console.WriteLine("Max is {0}, between {1} and {2}", max, maxStart, maxEnd);
}

public class Entry
{
    public DateTime dt;
    public int val;
}