我有几个有序的X/Y对列表,我想计算一个有序的X/Y对列表,代表这些列表的平均值.
所有这些列表(包括"平均列表")将被绘制到图表上(参见下面的示例图片).
我有几个问题:
我需要在C#中实现这一点,我想这对算法本身并不重要.

对不起,我无法以更正式或数学的方式解释我的问题.
编辑:我用"X/Y对列表"替换术语"功能",这不那么令人困惑.
我会使用贾斯汀提出的方法,并进行一次调整。他建议使用带有小数索引的映射表,但我建议使用整数索引。这听起来可能有点数学,但必须阅读以下两遍(我也必须这样做)并不丢人。假设 A 列表中索引 i 处的点已在另一个列表 B 中搜索最近的点,并且该最近点位于索引 j 处。要找到 B 中最接近 A[i+1] 的点,您应该只考虑 B 中索引等于或大于 j 的点。它可能是 j + 1,但也可能是 j 或 j + 2、j + 3 等,但绝不会低于 j。即使最接近 A[i+1] 的点的索引小于 j,您仍然不应该使用该点进行插值,因为这会导致意外的平均值和图形。我现在将花一点时间为您创建一些示例代码。我希望您看到这种优化是有意义的。
编辑:在实现这一点时,我意识到 j 不仅从下面有界(通过上述方法),而且从上面有界。当你尝试从A[i+1]到B[j]、B[j+1]、B[j+2]等的距离时,当A[i+1]到B[j+的距离...] 停止减少。在 B 中进一步搜索是没有意义的。同样的推理适用于当 j 从下面有界时:即使 B 中其他地方的某个点更接近,那也可能不是您想要插值的点。这样做会产生意想不到的图表,可能不如您预期的那么平滑。第二次绑定的额外好处是性能的提高。我创建了以下代码:
IEnumerable<Tuple<double, double>> Average(List<Tuple<double, double>> A, List<Tuple<double, double>> B)
{
if (A == null || B == null || A.Any(p => p == null) || B.Any(p => p == null)) throw new ArgumentException();
Func<double, double> square = d => d * d;//squares its argument
Func<int, int, double> euclidianDistance = (a, b) => Math.Sqrt(square(A[a].Item1 - B[b].Item1) + square(A[a].Item2 - B[b].Item2));//computes the distance from A[first argument] to B[second argument]
int previousIndexInB = 0;
for (int i = 0; i < A.Count; i++)
{
double distance = euclidianDistance(i, previousIndexInB);//distance between A[i] and B[j - 1], initially
for (int j = previousIndexInB + 1; j < B.Count; j++)
{
var distance2 = euclidianDistance(i, j);//distance between A[i] and B[j]
if (distance2 < distance)//if it's closer than the previously checked point, keep searching. Otherwise stop the search and return an interpolated point.
{
distance = distance2;
previousIndexInB = j;
}
else
{
break;//don't place the yield return statement here, because that could go wrong at the end of B.
}
}
yield return LinearInterpolation(A[i], B[previousIndexInB]);
}
}
Tuple<double, double> LinearInterpolation(Tuple<double, double> a, Tuple<double, double> b)
{
return new Tuple<double, double>((a.Item1 + b.Item1) / 2, (a.Item2 + b.Item2) / 2);
}
Run Code Online (Sandbox Code Playgroud)
供您参考,函数 Average 返回与列表 A 包含的相同数量的插值点,这可能没问题,但您应该针对您的特定应用程序考虑这一点。我在其中添加了一些注释以澄清一些细节,并且我在上面的文本中描述了此代码的所有方面。我希望它是清楚的,否则请随意提问。
第二次编辑:我误读了,以为你只有两个要点列表。我已经创建了上面接受多个列表的通用函数。它仍然只使用上面解释的那些原则。
IEnumerable<Tuple<double, double>> Average(List<List<Tuple<double, double>>> data)
{
if (data == null || data.Count < 2 || data.Any(list => list == null || list.Any(p => p == null))) throw new ArgumentException();
Func<double, double> square = d => d * d;
Func<Tuple<double, double>, Tuple<double, double>, double> euclidianDistance = (a, b) => Math.Sqrt(square(a.Item1 - b.Item1) + square(a.Item2 - b.Item2));
var firstList = data[0];
for (int i = 0; i < firstList.Count; i++)
{
int[] previousIndices = new int[data.Count];//the indices of points which are closest to the previous point firstList[i - 1].
//(or zero if i == 0). This is kept track of per list, except the first list.
var closests = new Tuple<double, double>[data.Count];//an array of points used for caching, of which the average will be yielded.
closests[0] = firstList[i];
for (int listIndex = 1; listIndex < data.Count; listIndex++)
{
var list = data[listIndex];
double distance = euclidianDistance(firstList[i], list[previousIndices[listIndex]]);
for (int j = previousIndices[listIndex] + 1; j < list.Count; j++)
{
var distance2 = euclidianDistance(firstList[i], list[j]);
if (distance2 < distance)//if it's closer than the previously checked point, keep searching. Otherwise stop the search and return an interpolated point.
{
distance = distance2;
previousIndices[listIndex] = j;
}
else
{
break;
}
}
closests[listIndex] = list[previousIndices[listIndex]];
}
yield return new Tuple<double, double>(closests.Select(p => p.Item1).Average(), closests.Select(p => p.Item2).Average());
}
}
Run Code Online (Sandbox Code Playgroud)
实际上,我分别为 2 个列表做了具体案例可能是一件好事:它很容易解释,并在理解通用版本之前提供了一个步骤。此外,可以取出平方根,因为它在排序时不会改变距离的顺序,只会改变长度。
第三次编辑:在评论中很明显可能存在错误。我认为除了提到的小错误之外,没有任何错误,除了图表末尾之外,应该没有任何区别。为了证明它确实有效,这是它的结果(虚线是平均值):
