C# 在动态范围内寻找局部极大值
在C#工作时,我需要在一个双倍列表中找到所有本地峰值,并将它们作为另一个双倍列表返回。如果我在任何给定的“窗口”值中比较一组值,这看起来很简单,但是我需要能够将这个窗口大小传递到函数本身。这可能令人困惑,但基本上我需要这样的东西:C# 在动态范围内寻找局部极大值,c#,algorithm,C#,Algorithm,在C#工作时,我需要在一个双倍列表中找到所有本地峰值,并将它们作为另一个双倍列表返回。如果我在任何给定的“窗口”值中比较一组值,这看起来很简单,但是我需要能够将这个窗口大小传递到函数本身。这可能令人困惑,但基本上我需要这样的东西: public List<double> FindPeaks(List<double> values, double rangeOfPeaks) 公共列表FindPeak(列表值,峰值的双范围) 如果“峰值范围”为5,则将“当前”值与每侧的2
public List<double> FindPeaks(List<double> values, double rangeOfPeaks)
公共列表FindPeak(列表值,峰值的双范围)
如果“峰值范围”为5,则将“当前”值与每侧的2个值进行比较,以确定其是否为峰值。如果“峰值范围”为11,则当前值将与每侧的5个值进行比较。我认为这是一个非常基本的算法,然而,我没有找到任何好的方法来检测这样的峰值。以前有人这样做过吗?任何帮助都将不胜感激。提前谢谢 可能有更有效的方法,但LINQ让这变得非常简单
static IList<double> FindPeaks(IList<double> values, int rangeOfPeaks)
{
List<double> peaks = new List<double>();
int checksOnEachSide = rangeOfPeaks / 2;
for (int i = 0; i < values.Count; i++)
{
double current = values[i];
IEnumerable<double> range = values;
if( i > checksOnEachSide )
range = range.Skip(i - checksOnEachSide);
range = range.Take(rangeOfPeaks);
if (current == range.Max())
peaks.Add(current);
}
return peaks;
}
静态IList FindPeak(IList值,int RangeofPeak)
{
列表峰值=新列表();
int checksOnEachSide=峰值范围/2;
for(int i=0;i选中每个选项)
range=range.Skip(i-选中每个字符串);
range=range.Take(峰值范围);
如果(当前==范围.Max())
峰值。添加(当前);
}
返回峰值;
}
此函数为O(n)。它在运行时产生结果,因此它的内存开销也非常低
public static IEnumerable<double> FindPeaks(IEnumerable<double> values, int rangeOfPeaks)
{
double peak = 0;
int decay = 0;
foreach (var value in values)
{
if (value > peak || decay > rangeOfPeaks / 2)
{
peak = value;
decay = 0;
}
else
{
decay++;
}
if (decay == rangeOfPeaks / 2)
yield return peak;
}
}
公共静态IEnumerable FindPeak(IEnumerable值,int RangeofPeak)
{
双峰=0;
int衰减=0;
foreach(值中的var值)
{
if(值>峰值| |衰减>峰值范围/2)
{
峰值=峰值;
衰减=0;
}
其他的
{
衰变++;
}
if(衰减==峰值范围/2)
收益率峰值;
}
}
我建议对Levy的帖子做一些修改
1) 当指定的IList值几乎是直线时,Levy的代码抛出了一个异常
2) 我认为数组中峰值的索引是理想的结果。例如,如果我们有两个相同的双峰,将会发生什么?行动计划。更改为返回指定IList中的峰值索引
public static IList<int> FindPeaks(IList<double> values, int rangeOfPeaks)
{
List<int> peaks = new List<int>();
double current;
IEnumerable<double> range;
int checksOnEachSide = rangeOfPeaks / 2;
for (int i = 0; i < values.Count; i++)
{
current = values[i];
range = values;
if (i > checksOnEachSide)
{
range = range.Skip(i - checksOnEachSide);
}
range = range.Take(rangeOfPeaks);
if ((range.Count() > 0) && (current == range.Max()))
{
peaks.Add(i);
}
}
return peaks;
}
公共静态IList FindPeak(IList值,int RangeofPeak)
{
列表峰值=新列表();
双电流;
可数范围;
int checksOnEachSide=峰值范围/2;
for(int i=0;i选中每个选项)
{
range=range.Skip(i-选中每个字符串);
}
range=range.Take(峰值范围);
如果((range.Count()>0)和&(current==range.Max())
{
增加(i);
}
}
返回峰值;
}
这个老问题已经有了公认的答案,但我想要比O(n^2)更好的答案。这个函数是O(n*m),其中m是窗口大小,并且具有实际工作的优点。该方法返回局部极大值索引的元组及其关联值
调用Enumerable.Repeat()
确保在集合的最开始和结尾都找到最大值
与之后的队列比较使用=
,以便在值平台的开始处找到局部最大值。副作用是,如果集合中的所有值都相等,则返回索引0处的值,这可能是可取的,也可能是不可取的
public static IEnumerable<Tuple<int, double>> LocalMaxima( IEnumerable<double> source, int windowSize )
{
// Round up to nearest odd value
windowSize = windowSize - windowSize % 2 + 1;
int halfWindow = windowSize / 2;
int index = 0;
var before = new Queue<double>( Enumerable.Repeat( double.NegativeInfinity, halfWindow ) );
var after = new Queue<double>( source.Take( halfWindow + 1 ) );
foreach( double d in source.Skip( halfWindow + 1 ).Concat( Enumerable.Repeat( double.NegativeInfinity, halfWindow + 1 ) ) )
{
double curVal = after.Dequeue();
if( before.All( x => curVal > x ) && after.All( x => curVal >= x ) )
{
yield return Tuple.Create( index, curVal );
}
before.Dequeue();
before.Enqueue( curVal );
after.Enqueue( d );
index++;
}
}
public静态IEnumerable LocalMaxima(IEnumerable源代码,int windowSize)
{
//四舍五入到最近的奇数
windowSize=windowSize-windowSize%2+1;
int halfWindow=windowSize/2;
int指数=0;
var before=新队列(Enumerable.Repeat(double.NegativeInfinity,halfWindow));
var after=新队列(source.Take(半窗口+1));
foreach(source.Skip(halfWindow+1).Concat(Enumerable.Repeat(double.NegativeInfinity,halfWindow+1))中的双d)
{
double curVal=after.Dequeue();
if(在.All之前(x=>curVal>x)和&after.All之后(x=>curVal>=x))
{
产生返回元组。创建(索引,曲线);
}
before.Dequeue();
排队前(曲线);
排队后(d);
索引++;
}
}
使用Rx团队提供的,您可以非常巧妙地解决此问题。该软件包有很多函数用于不同的缓冲/窗口场景
IEnumerable<double> FindPeaks(IEnumerable<double> numbers, int windowSize)
{
// Pad numbers to the left of <numbers> so that the first window of <windowSize> is centred on the first item in <numbers>
// Eg if numbers = { 1, 2, 3, 4 }, windowSize = 3, the first window should be { MinValue, 1, 2 }, not { 1, 2, 3 }
var paddedNumbers = Enumerable.Repeat(double.MinValue, windowSize / 2)
.Concat(numbers);
// Take buffers of size <windowSize>, stepping forward by one element each time
var peaks = paddedNumbers.Buffer(windowSize, 1)
.Select(range => range.Max())
.DistinctUntilChanged();
return peaks;
}
IEnumerable FindPeaks(IEnumerable number,int windowSize)
{
//将编号放置在的左侧,以便的第一个窗口以中的第一个项目为中心
//例如,如果numbers={1,2,3,4},windowSize=3,则第一个窗口应该是{MinValue,1,2},而不是{1,2,3}
var paddedNumbers=Enumerable.Repeat(double.MinValue,windowSize/2)
.Concat(数字);
//取大小的缓冲区,每次前进一个元素
var peaks=paddedNumbers.Buffer(窗口大小,1)
.Select(范围=>range.Max())
.DistinctUntilChanged();
返回峰值;
}
这是我的版本。它使用队列
来保存最后一个windowSize
元素,同时枚举源。不幸的是,我不得不使用低效的Linq方法在队列
中查找被测试的元素,因为队列
实现没有公开其方法(它是内部的)。对于较小的窗口大小,这应该不是问题
public static IEnumerable<(int, TSource)> LocalMaxima<TSource>(
this IEnumerable<TSource> source, int windowSize)
{
var comparer = Comparer<TSource>.Default;
var queue = new Queue<TSource>();
var testedQueueIndex = (windowSize - 1) / 2;
var index = testedQueueIndex;
foreach (var item in source)
{
queue.Enqueue(item);
if (queue.Count >= windowSize)
{
var testedItem = queue.ElementAt(testedQueueIndex);
var queueIndex = 0;
foreach (var queuedItem in queue)
{
if (queueIndex != testedQueueIndex
&& comparer.Compare(queuedItem, testedItem) > 0) goto next;
queueIndex++;
}
yield return (index, testedItem);
next:
queue.Dequeue();
index++;
}
}
}
输出:
Source: a, b, b, a, c, d, b, b, c, a, c
Indexes: 0 1 2 3 4 5 6 7 8 9 10
Result: (5, d), (8, c)
到
Source: a, b, b, a, c, d, b, b, c, a, c
Indexes: 0 1 2 3 4 5 6 7 8 9 10
Result: (5, d), (8, c)
public static IEnumerable<Tuple<int, double>> LocalMaxima( IEnumerable<double> source, int windowSize )
{
// Round up to nearest odd value
windowSize = windowSize - windowSize % 2 + 1;
int halfWindow = windowSize / 2;
int index = 0;
var before = new Queue<double>( Enumerable.Repeat( double.NegativeInfinity, halfWindow ) );
var after = new Queue<double>( source.Take( halfWindow + 1 ) );
foreach( double d in source.Skip( halfWindow + 1 ).Concat( Enumerable.Repeat( double.NegativeInfinity, halfWindow + 1 ) ) )
{
double curVal = after.Dequeue();
if( before.All( x => curVal > x ) && after.All( x => curVal >= x ) )
{
yield return Tuple.Create( index, curVal );
}
before.Enqueue( curVal );
before.Dequeue();
after.Enqueue( d );
index++;
}
}