在C#中工作,我需要在列表中找到所有本地峰值,然后将它们作为另一个列表再次返回。这看起来很简单,如果我在任何给定的“窗口”值中有一组我正在比较的值,但我需要能够将这个窗口大小实际传递给函数本身。这可能令人困惑,但基本上我需要这样的东西:
public List<double> FindPeaks(List<double> values, double rangeOfPeaks)
如果'rangeOfPeaks'为5,则将'current'值与其每一侧的2个值进行比较,以确定它是否为峰值。如果'rangeOfPeaks'为11,则将当前值与每侧的5个值进行比较。我认为这是一个非常基本的算法,但是,我找不到像这样检测峰值的任何好方法都没有成功。有没有人曾经这样做过?任何帮助都将不胜感激。提前谢谢!
答案 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++;
}
}