在动态范围内查找局部最大值

时间:2011-03-11 04:37:38

标签: c# algorithm

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

public List<double> FindPeaks(List<double> values, double rangeOfPeaks)

如果'rangeOfPeaks'为5,则将'current'值与其每一侧的2个值进行比较,以确定它是否为峰值。如果'rangeOfPeaks'为11,则将当前值与每侧的5个值进行比较。我认为这是一个非常基本的算法,但是,我找不到像这样检测峰值的任何好方法都没有成功。有没有人曾经这样做过?任何帮助都将不胜感激。提前谢谢!

7 个答案:

答案 0 :(得分:8)

我建议对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;
    }

答案 1 :(得分:7)

可能有更有效的方法,但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;
    }

答案 2 :(得分:1)

旧问题已经有了接受的答案,但我想要比O(n ^ 2)更好的东西。这个函数是O(n * m),其中 m 是窗口大小,并且具有实际工作的优点。该方法返回局部最大值索引的元组及其相关值。

Enumerable.Repeat()的调用也确保了在集合的开头和结尾处的最大值。

after队列的比较使用>=,以便在值的平台开始时找到局部最大值。副作用是如果集合中的所有值都相等,则返回索引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++;
    }
}

答案 3 :(得分:0)

此功能为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;
        }
    }

答案 4 :(得分:0)

使用Rx团队的Interactive Extensions package,您可以非常巧妙地解决这个问题。该软件包具有许多与不同缓冲/窗口方案相关的功能。

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

答案 5 :(得分:0)

这是我的版本。它使用Queue来保存最后的windowSize元素,同时枚举源。不幸的是,我不得不使用效率低下的ElementAt Linq方法在Queue中找到经过测试的元素,因为Queue实现并没有公开其GetElement方法(内部)。 。对于较小的窗口,这应该不是问题。

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

用法示例:

var source = "abbacdbbcac".ToCharArray();
var indexes = Enumerable.Range(0, source.Length);
var result = source.LocalMaxima(5);
Console.WriteLine($"Source:  {String.Join(", ", source)}");
Console.WriteLine($"Indexes: {String.Join("  ", indexes)}");
Console.WriteLine($"Result:  {String.Join(", ", result)}");

输出:

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)

答案 6 :(得分:0)

要允许窗口大小为1而不会引发异常,我翻转了Jeroen Cranendonk贡献的before.Enqueue( curVal );before.Dequeue();函数:

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