我已将数值数据及其坐标(xValues、yValues)存储在列表中,如果我想将这组数据与另一组数据进行比较(加、减、除...),我必须知道我可以如果 xValues 不匹配,则不进行比较(因为没有任何内容可比较)。因此,我需要在实际存在于其他集合中的“缺失”xValue 之间进行线性插值并生成新点。请检查这张图片:
红线上的青色方 block 代表存储的点 (xValues2),并且(通常)它们不会与对方设置的 xValues (xValues1) 匹配。绿线上的两个方 block 是所需生成点的示例。有了它们,我就可以毫无问题地处理这两个图表。
对于线性插值,这非常简单:如果我有两个点 (x0,y0) 和 (x1,y1),并且我想在给定“x2”的情况下在它们之间添加一个新点:
y2=y0+(x2-x0)*(y1-y0)/(x1-x0)
为了使这项工作成功,我想我必须实现这样的东西:
- 创建新列表(xValuesNew、yValuesNew)。
- 在 xValues1 和 xValues2 之间建立并集 (xValuesNew)。
- 检查原始 xValues1 和 xValuesNew 之间有何差异。
- 对于找到的每个新值,使用上面编写的公式生成“y”。
- 将这 4 个步骤放入一个方法中并再次使用它,但现在使用 set2。
我一整天都在研究这个问题,试图找到一个简单的解决方案,也许使用 Linq 或 lambda 表达式,但我不习惯使用它们,而且我对这些主题缺乏了解。请注意,此操作会经常进行,因此我必须使其不要太重。我认为出于这个原因,生成一个新列表而不是在原始列表中间插入点将是一个好主意。
如果有人可以指导我一点或者告诉我是否有一个数学库实际上在做这件事,那就太好了。谢谢。
编辑:抱歉,如果我没有正确解释我的意思。
这里我有一个示例(在 Excel 中完成):
请注意,我无法直接将 Series1 和 Series2 (+) 相加或进行任何其他操作,因为它们的 X 间距不同。所以我想要的是在需要时在 Series1 中生成一个新点。
为此,我想简单地使用线性插值。假设我在系列 1 中有 P1(0,40) 和 P2(0,60),但在系列 2 中我有一个点 (1,10)。我需要在 P1 和 P2 之间生成一个坐标为 (1,50) 的点 P3。
我尝试使用 SkipWhile 执行此操作,并比较两个系列的下一个 X 值,如果 series1 的 XValue 较低,则在 newSeries 中添加该 XValue 和相应的 YValue。否则使用 XValue2 生成 Y 并将其添加到 newSeries 中。这是我的尝试之一(不起作用):
List<double> series1X = new List<double> { 0, 2, 4, 6, 8 };
List<double> series1Y = new List<double> { 120, 100, 110, 105, 70 };
List<double> series2X = new List<double> { 0, 1, 7, 8,9 };
List<double> newSeries1X = new List<double>();
List<double> newSeries1Y = new List<double>();
double lastX1 = series1X[series1X.Count()-1];
int i = 0;
while (next1X <= lastX1)
{
next2X = series2X.SkipWhile(p => p <= next1X).First();
Console.WriteLine(next2X.ToString());
if (next1X <= next2X)
{
newSeries1X.Add(series1X[i]);
newSeries1Y.Add(series1Y[i]);
}
if (next2X < next1X)
{
while (next2X < next1X)
{
newSeries1X.Add(next2X);
newY = series1Y[i] + (next2X - series1X[i]) * (series1Y[i + 1] - series1Y[i]) / (series1X[i + 1] - series1X[i]);
newSeries1Y.Add(newY);
next2X = series2X.SkipWhile(p => p <= next2X).First();
}
}
next1X = series1X.SkipWhile(p => p <= next2X).First();
Console.WriteLine(next1X.ToString());
i++;
}
用你的 Zip 方法来做到这一点真是太棒了。但我不知道如何在谓词中写下这个条件。
最佳答案
首先,我可能会使用一个适当的“点”类,其中包含 x 和 y 坐标,而不是为每个坐标使用两个单独的列表。然后您可以使用 Zip
快速迭代它们的方法:
IEnumerable<PointF> points0 = ...
IEnumerable<PointF> points0 = ...
float x2 = ...
IEnumerable<PointF> newPoints = point0.Zip(points1,
(p0, p1) => new PointF(p0.X, p0.Y + (x2-p0.X) * (p1.Y-p0.Y) / (p1.X-p0.X)));
这使得您可以轻松地根据输入数据计算一组新的点。如果您只关心单个 y 值,您仍然可以使用当前数据执行此操作,但它看起来会很奇怪:
IEnumerable<double> y2values =
xValues1.Zip(yValues1, (x, y) => new { x, y }).Zip(
xValues2.Zip(yValues2, (x, y) => new { x, y }),
(p0, p1) => p0.y + (x2-p0.x) * (p1.y-p0.y) / (p1.x-p0.x));
如果在编码这个答案的过程中我以某种方式破坏了你的数学,我深表歉意。
更新
现在我对您想要做的事情有了更好的了解,我认为任何 Linq 方法都不会完全正确。这是我使用索引得出的结论:
List<double> series1X = new List<double> { 0, 2, 4, 6, 8 };
List<double> series1Y = new List<double> { 120, 100, 110, 105, 70 };
List<double> series2X = new List<double> { 0, 1, 7, 8, 9 };
// in the worst case there are n + m new points
List<double> newSeries1X = new List<double>(series1X.Count + series2X.Count);
List<double> newSeries1Y = new List<double>(series1X.Count + series2X.Count);
int i = 0, j = 0;
for ( ; i < series1X.Count && j < series2X.Count; )
{
if (series1X[i] <= series2X[j])
{
newSeries1X.Add(series1X[i]);
newSeries1Y.Add(series1Y[i]);
if (series1X[i] == series2X[j])
{
j++;
}
i++;
}
else
{
int k = (i == 0) ? i : i - 1;
// interpolate
double y0 = series1Y[k];
double y1 = series1Y[k + 1];
double x0 = series1X[k];
double x1 = series1X[k + 1];
double y = y0 + (y1 - y0) * (series2X[j] - x0) / (x1 - x0);
newSeries1X.Add(series2X[j]);
newSeries1Y.Add(y);
j++;
}
}
for ( ; i < series1X.Count; i++)
{
newSeries1X.Add(series1X[i]);
newSeries1Y.Add(series1Y[i]);
}
for ( ; j < series2X.Count; j++)
{
// interpolate
double y0 = series1Y[i - 2];
double y1 = series1Y[i - 1];
double x0 = series1X[i - 2];
double x1 = series1X[i - 1];
double y = y0 + (y1 - y0) * (series2X[j] - x0) / (x1 - x0);
newSeries1X.Add(series2X[j]);
newSeries1Y.Add(y);
}
输出为
newSeries1X = { 0, 1, 2, 4, 6, 7, 8, 0 }
newSeries1Y = { 120, 110, 100, 110, 105, 87.5, 70, 52.5 }
此解决方案处理第一个 series2X[0] < series1X[0]
的情况当 series2X[n] > series1X[m]
通过从第一对/最后一对点向外线性“投影”数据。
这是使用枚举器(大部分)的另一个解决方案,但它并不像我希望的那么优雅。它可能可以改进一点:
bool hasS1 = true, hasS2 = true, preinterp = true;
double x0 = 0, y0 = 0, x1 = 0, y1 = 0, x = 0, y = 0;
using(var s1xEnumerator = series1X.GetEnumerator())
using(var s1yEnumerator = series1Y.GetEnumerator())
using(var s2xEnumerator = series2X.GetEnumerator())
{
hasS1 = s1xEnumerator.MoveNext();
hasS2 = s2xEnumerator.MoveNext();
s1yEnumerator.MoveNext();
while(hasS1 && hasS2)
{
x1 = s1xEnumerator.Current;
y1 = s1yEnumerator.Current;
x = s2xEnumerator.Current;
if (x1 <= x)
{
newSeries1X.Add(x1);
newSeries1Y.Add(y1);
hasS1 = s1xEnumerator.MoveNext();
s1yEnumerator.MoveNext();
preinterp = false;
if (hasS1)
{
x0 = x1;
y0 = y1;
}
if (x1 == x)
{
hasS2 = s2xEnumerator.MoveNext();
}
}
else
{
// we have to look ahead to get the next interval to interpolate before x0
if (preinterp)
{
x0 = x1;
y0 = y1;
x1 = series1X[1]; // can't peek with enumerator
y1 = series1Y[1];
preinterp = false;
}
y = y0 + (y1 - y0) * (x - x0) / (x1 - x0);
newSeries1X.Add(x);
newSeries1Y.Add(y);
hasS2 = s2xEnumerator.MoveNext();
}
}
while(hasS1)
{
newSeries1X.Add(s1xEnumerator.Current);
newSeries1Y.Add(s1yEnumerator.Current);
hasS1 = s1xEnumerator.MoveNext();
s1yEnumerator.MoveNext();
}
while(hasS2)
{
x = s2xEnumerator.Current;
y = y0 + (y1 - y0) * (x - x0) / (x1 - x0);
newSeries1X.Add(x);
newSeries1Y.Add(y);
hasS2 = s2xEnumerator.MoveNext();
}
}
关于c# - 比较两组数据 (x,y),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17118513/