C# 在动态范围内寻找局部极大值

C# 在动态范围内寻找局部极大值,c#,algorithm,C#,Algorithm,在C#工作时,我需要在一个双倍列表中找到所有本地峰值,并将它们作为另一个双倍列表返回。如果我在任何给定的“窗口”值中比较一组值,这看起来很简单,但是我需要能够将这个窗口大小传递到函数本身。这可能令人困惑,但基本上我需要这样的东西: public List<double> FindPeaks(List<double> values, double rangeOfPeaks) 公共列表FindPeak(列表值,峰值的双范围) 如果“峰值范围”为5,则将“当前”值与每侧的2

在C#工作时,我需要在一个双倍列表中找到所有本地峰值,并将它们作为另一个双倍列表返回。如果我在任何给定的“窗口”值中比较一组值,这看起来很简单,但是我需要能够将这个窗口大小传递到函数本身。这可能令人困惑,但基本上我需要这样的东西:

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