我有几个有序的X / Y对列表,我想计算一个有序的X / Y对列表,代表这些列表的平均值。
所有这些列表(包括“平均列表”)将被绘制到图表上(参见下面的示例图片)。
我有几个问题:
我需要在C#中实现这一点,我想这对算法本身并不重要。
抱歉,我无法以更正式或数学的方式解释我的问题。
编辑:我用“X / Y对列表”替换了“功能”这个术语,这不那么令人困惑。
答案 0 :(得分:4)
我将使用你的功能的比喻,即汽车在弯曲的赛道上奔跑,在那里你想要根据汽车的位置提取赛道的中心线。每辆车的位置都可以描述为时间的函数:
p1(t) = (x1(t), y1(t))
p2(t) = (x2(t), y2(t))
p3(t) = (x3(t), y3(t))
关键问题是赛车以不同的速度赛车,这意味着p1(10)
可能是赛道上p2(10)
的两倍。{{1}}。如果你对这两个点采取了天真的平均值,并且赛车之间的轨道有一条尖锐的曲线,那么平均值可能远离赛道。
如果你只是转换你的功能不再是时间的函数,而是沿着轨道的距离的函数,那么你就可以做到你想要什么。
您可以这样做的一种方法是选择最慢的汽车(即具有最多样本数量的汽车)。然后,对于最慢车辆位置的每个样本,查看所有其他车辆的路径,找到两个最近的点,并选择插值线上最接近最慢车辆位置的点。然后将这些点平均在一起。对所有慢车样本执行此操作后,您将获得平均路径。
我假设所有的汽车都在大致相同的地方开始和结束;如果任何一辆赛车只占赛道的一小部分,你需要增加一些逻辑来检测它。
可能的改进(性能和准确性)是跟踪每辆车使用的最新样本和每辆车的速度(相对采样率)。对于你最慢的车,这将是一个简单的地图:1 => 1,2 => 2,3 => 3,......对于其他车型,它可能更像是:1 => 0.3,2 => 0.7,3 => 1.6(小数值是由插值引起的)。速度将是样本数量变化的倒数(例如,慢车将具有速度1,而另一辆车具有速度1 /(1.6-0.7)= 1.11)。然后,您可以确保不会意外地回溯任何车辆。您还可以提高计算速度,因为您不必搜索每条路径上的所有点的整个集合;相反,您可以假设下一个样本将接近当前样本加上1 /速度。
答案 1 :(得分:4)
我会使用贾斯汀提出的方法,只需一次调整。他建议使用带有小数指数的映射表,但我建议使用整数指数。这可能听起来有点数学,但是必须阅读以下两次并不是羞耻(我也必须这样做)。假设对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);
}
对于您的信息,函数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());
}
}
实际上我分别为2个列表做了具体案例可能是一件好事:它很容易解释并在理解通用版本之前提供了一个步骤。此外,可以取出平方根,因为它在排序时不会改变距离的顺序,只是长度。
第三次编辑:在评论中很明显可能存在错误。我认为除了上面提到的小虫子之外没有其他的东西,除了图表的末尾之外不应该有任何区别。作为它实际工作的证明,这是它的结果(虚线是平均值):
答案 2 :(得分:2)
由于这些不是y=f(x)
函数,它们可能类似于(x,y)=f(t)
吗?
如果是这样,你可以沿t插值,并为每个t计算avg(x)和avg(y)。
编辑这当然假设t可以用于您的代码 - 这样您就拥有了T / X / Y三元组的有序列表。
答案 3 :(得分:0)
有几种方法可以做到这一点。一种是将所有数据组合成一组单独的点,并通过组合集做出最佳拟合曲线。
答案 4 :(得分:0)
fc1 = { {1,0.3} {2, 0.5} {3, 0.1} }
fc1 = { {1,0.1} {2, 0.8} {3, 0.4} }
你想要两个函数的算术平均值(俚语:“平均值”)。为此,您只需计算逐点算术平均值:
fc3 = { {1, (0.3+0.1)/2} ... }
优化: 如果您有大量的积分,您应首先将“有序的X / Y对列表”转换为矩阵或至少按列存储点,如下所示: {0.3,0.1},{0.5,0.8},{0.1,0.4}