如果下面的词典中有一系列带有日期时间的数字,那么以编程方式选择特定时间段的最大总和范围的最有效方法是什么?下面只是一个例子,但我的实际数据在字典中有超过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,但没有必要)? 感谢您的帮助。
答案 0 :(得分:1)
如果您正在寻找效率,LINQ不会帮助您。
我的脑子里有蛮力算法:
这里的来源(我借用了解析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;
}