C# 高效滚动最大和最小窗口
我想有效地计算滚动最大值和最小值。这意味着在每次窗口移动时都要从使用的所有值中重新计算最大值/最小值 这里有一篇帖子问了同样的问题,有人发布了一个解决方案,涉及某种堆栈方法,根据其评级,这种方法应该是有效的。然而,为了我的生命,我再也找不到它了C# 高效滚动最大和最小窗口,c#,performance,C#,Performance,我想有效地计算滚动最大值和最小值。这意味着在每次窗口移动时都要从使用的所有值中重新计算最大值/最小值 这里有一篇帖子问了同样的问题,有人发布了一个解决方案,涉及某种堆栈方法,根据其评级,这种方法应该是有效的。然而,为了我的生命,我再也找不到它了 如果您能帮助我们找到解决方案或帖子,我们将不胜感激。谢谢大家! 要使用的算法称为升序极小值 要在C#中实现这一点,您需要获得一个类,并且NuGet上有一个好的类,名称为 我已经使用Nito.Deque编写了一个快速的C#实现,但是我只是简单地检查了一下,
如果您能帮助我们找到解决方案或帖子,我们将不胜感激。谢谢大家! 要使用的算法称为升序极小值 要在C#中实现这一点,您需要获得一个类,并且NuGet上有一个好的类,名称为 我已经使用Nito.Deque编写了一个快速的C#实现,但是我只是简单地检查了一下,并且是在我的头脑中完成的,所以可能是错误的
public static class AscendingMinima
{
private struct MinimaValue
{
public int RemoveIndex { get; set; }
public double Value { get; set; }
}
public static double[] GetMin(this double[] input, int window)
{
var queue = new Deque<MinimaValue>();
var result = new double[input.Length];
for (int i = 0; i < input.Length; i++)
{
var val = input[i];
// Note: in Nito.Deque, queue[0] is the front
while (queue.Count > 0 && i >= queue[0].RemoveIndex)
queue.RemoveFromFront();
while (queue.Count > 0 && queue[queue.Count - 1].Value >= val)
queue.RemoveFromBack();
queue.AddToBack(new MinimaValue{RemoveIndex = i + window, Value = val });
result[i] = queue[0].Value;
}
return result;
}
}
公共静态类递增最小值
{
私有结构最小值
{
public int RemoveIndex{get;set;}
公共双值{get;set;}
}
公共静态双精度[]GetMin(此双精度[]输入,int窗口)
{
var queue=new Deque();
var结果=新的双精度[input.Length];
for(int i=0;i0&&i>=队列[0].RemoveIndex)
queue.RemoveFromFront();
while(queue.Count>0&&queue[queue.Count-1]。值>=val)
queue.RemoveFromBack();
AddToBack(新的最小值{RemoveIndex=i+window,Value=val});
结果[i]=队列[0]。值;
}
返回结果;
}
}
这里有一种更有效的方法。您仍然需要偶尔计算该值,但除了某些退化数据(不断减小的值),在该解决方案中,该值被最小化
我们将把自己限制到最大限度来简化事情,但将其扩展到最小限度也很简单
您只需要以下内容:
- 窗口本身,最初为空
- 当前最大值(
),最初为任意值max
- 当前最大值(
)的计数,最初为零maxcount
max
和maxcount
作为缓存来保存当前最大值。在缓存有效的地方,您只需要返回其中的值,这是一个非常快速的常量时间操作
如果请求最大值时缓存无效,它将填充缓存,然后返回该值。这比上一段中的方法慢,但缓存有效后的最大值请求再次使用更快的方法
以下是维护窗口和相关数据的方法:
N
M
。如果最大计数大于0且M
等于max
,则减小maxcount
。一旦maxcount
达到0,缓存无效,但我们不需要担心,直到用户请求最大值(在此之前没有必要重新填充缓存)N
添加到滚动窗口N
是唯一的当前条目),请将max
设置为N
并将maxcount
设置为1,然后返回步骤1maxcount
大于0且N
大于max
,则将max
设置为N
且maxcount
设置为1,然后返回步骤1maxcount
大于0且N
等于max
,则增加maxcount
maxcount
大于0,则缓存有效:只需返回max
max
和maxcount
事实上,您主要维护一个最大值的缓存,并且只在需要时重新计算,这比在添加条目时盲目地重新计算要有效得多 为了获得一些明确的统计信息,我创建了以下Python程序。它使用大小为25的滑动窗口,并使用0到999(含0到999)的随机数(您可以使用这些属性查看它们如何影响结果) 首先是一些初始化代码。注意
stat
变量,它们将用于计算缓存命中率和未命中率:
import random
window = []
max = 0
maxcount = 0
maxwin = 25
statCache = 0
statNonCache = 0
然后,根据我上面的描述,向窗口添加数字的功能:
def addNum(n):
global window
global max
global maxcount
if len(window) == maxwin:
m = window[0]
window = window[1:]
if maxcount > 0 and m == max:
maxcount = maxcount - 1
window.append(n)
if len(window) == 1:
max = n
maxcount = 1
return
if maxcount > 0 and n > max:
max = n
maxcount = 1
return
if maxcount > 0 and n == max:
maxcount = maxcount + 1
接下来,从窗口返回最大值的代码:
def getMax():
global max
global maxcount
global statCache
global statNonCache
if len(window) == 0:
return None
if maxcount > 0:
statCache = statCache + 1
return max
max = window[0]
maxcount = 0
for val in window:
if val > max:
max = val
maxcount = 1
else:
if val == max:
maxcount = maxcount + 1
statNonCache = statNonCache + 1
return max
最后,测试线束:
random.seed()
for i in range(1000000):
val = int(1000 * random.random())
addNum(val)
newmax = getMax()
print("%d cached, %d non-cached"%(statCache,statNonCache))
请注意,每次向窗口添加数字时,测试线束都会尝试获得最大值。在实践中,可能不需要这样做。换句话说,这是生成的随机数据的最坏情况
为了伪统计的目的运行该程序几次,我们得到(为报告目的格式化和分析): 因此,您可以看到,对于随机数据,平均而言,只有大约3.95%的情况会导致计算命中(缓存未命中)。绝大多数使用缓存的值。这应该比每次插入窗口时必须重新计算最大值要好得多 一些
random.seed()
for i in range(1000000):
val = int(1000 * random.random())
addNum(val)
newmax = getMax()
print("%d cached, %d non-cached"%(statCache,statNonCache))
960579 cached, 39421 non-cached
960373 cached, 39627 non-cached
960395 cached, 39605 non-cached
960348 cached, 39652 non-cached
960441 cached, 39559 non-cached
960602 cached, 39398 non-cached
960561 cached, 39439 non-cached
960463 cached, 39537 non-cached
960409 cached, 39591 non-cached
960798 cached, 39202 non-cached
======= ======
9604969 395031
static class Program
{
static Random r = new Random();
static int Window = 50; //(small to facilitate visual functional test). eventually could be 100 1000, but not more than 5000.
const int FullDataSize =1000;
static double[] InputArr = new double[FullDataSize]; //array prefilled with the random input data.
//====================== Caching algo variables
static double Low = 0;
static int LowLocation = 0;
static int CurrentLocation = 0;
static double[] Result1 = new double[FullDataSize]; //contains the caching mimimum result
static int i1; //incrementor, just to store the result back to the array. In real life, the result is not even stored back to array.
//====================== Ascending Minima algo variables
static double[] Result2 = new double[FullDataSize]; //contains ascending miminum result.
static double[] RollWinArray = new double[Window]; //array for the caching algo
static Deque<MinimaValue> RollWinDeque = new Deque<MinimaValue>(); //Niro.Deque nuget.
static int i2; //used by the struct of the Deque (not just for result storage)
//====================================== my initialy proposed caching algo
static void CalcCachingMin(double currentNum)
{
RollWinArray[CurrentLocation] = currentNum;
if (currentNum <= Low)
{
LowLocation = CurrentLocation;
Low = currentNum;
}
else if (CurrentLocation == LowLocation)
ReFindHighest();
CurrentLocation++;
if (CurrentLocation == Window) CurrentLocation = 0; //this is faster
//CurrentLocation = CurrentLocation % Window; //this is slower, still over 10 fold faster than ascending minima
Result1[i1++] = Low;
}
//full iteration run each time lowest is overwritten.
static void ReFindHighest()
{
Low = RollWinArray[0];
LowLocation = 0; //bug fix. missing from initial version.
for (int i = 1; i < Window; i++)
if (RollWinArray[i] < Low)
{
Low = RollWinArray[i];
LowLocation = i;
}
}
//======================================= Ascending Minima algo based on http://stackoverflow.com/a/14823809/2381899
private struct MinimaValue
{
public int RemoveIndex { get; set; }
public double Value { get; set; }
}
public static void CalcAscendingMinima (double newNum)
{ //same algo as the extension method below, but used on external arrays, and fed with 1 data point at a time like in the projected real time app.
while (RollWinDeque.Count > 0 && i2 >= RollWinDeque[0].RemoveIndex)
RollWinDeque.RemoveFromFront();
while (RollWinDeque.Count > 0 && RollWinDeque[RollWinDeque.Count - 1].Value >= newNum)
RollWinDeque.RemoveFromBack();
RollWinDeque.AddToBack(new MinimaValue { RemoveIndex = i2 + Window, Value = newNum });
Result2[i2++] = RollWinDeque[0].Value;
}
public static double[] GetMin(this double[] input, int window)
{ //this is the initial method extesion for ascending mimima
//taken from http://stackoverflow.com/a/14823809/2381899
var queue = new Deque<MinimaValue>();
var result = new double[input.Length];
for (int i = 0; i < input.Length; i++)
{
var val = input[i];
// Note: in Nito.Deque, queue[0] is the front
while (queue.Count > 0 && i >= queue[0].RemoveIndex)
queue.RemoveFromFront();
while (queue.Count > 0 && queue[queue.Count - 1].Value >= val)
queue.RemoveFromBack();
queue.AddToBack(new MinimaValue { RemoveIndex = i + window, Value = val });
result[i] = queue[0].Value;
}
return result;
}
//============================================ Test program.
static void Main(string[] args)
{ //this it the test program.
//it runs several attempts of both algos on the same data.
for (int j = 0; j < 10; j++)
{
Low = 12000;
for (int i = 0; i < Window; i++)
RollWinArray[i] = 10000000;
//Fill the data + functional test - generate 100 numbers and check them in as you go:
InputArr[0] = 12000;
for (int i = 1; i < FullDataSize; i++) //fill the Input array with random data.
//InputArr[i] = r.Next(100) + 11000;//simple data.
InputArr[i] = InputArr[i - 1] + r.NextDouble() - 0.5; //brownian motion data.
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < FullDataSize; i++) //run the Caching algo.
CalcCachingMin(InputArr[i]);
stopwatch.Stop();
Console.WriteLine("Caching : " + stopwatch.ElapsedTicks + " mS: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < FullDataSize; i++) //run the Ascending Minima algo
CalcAscendingMinima(InputArr[i]);
stopwatch.Stop();
Console.WriteLine("AscMimima: " + stopwatch.ElapsedTicks + " mS: " + stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
i1 = 0; i2 = 0; RollWinDeque.Clear();
}
for (int i = 0; i < FullDataSize; i++) //test the results.
if (Result2[i] != Result1[i]) //this is a test that algos are valid. Errors (mismatches) are printed.
Console.WriteLine("Current:" + InputArr[i].ToString("#.00") + "\tLowest of " + Window + "last is " + Result1[i].ToString("#.00") + " " + Result2[i].ToString("#.00") + "\t" + (Result1[i] == Result2[i])); //for validation purposes only.
Console.ReadLine();
}
}