C#实现文本文件数据邻近值匹配与线性插值优化方案
实现带插值功能的高效数据查询函数
我有一个包含10000行数据的文本文件,内容格式示例如下:
<p>16500000000 -11.6560625775</p> <p>16600000000 -11.99428283515</p> <p>16700000000 -12.06410749998</p> <p>16800000000 -11.81220239236</p>
需要编写一个C#函数,接收文件路径和第一列的目标数值,返回对应的第二列数值;若目标数值未在第一列中找到,则通过线性插值计算结果。例如:
- 输入
16600000000时返回-11.99428283515 - 输入
16650000000时需在16600000000与16700000000对应值间插值
目前已有无插值功能的代码,现寻求优化实现插值且保证高效的方案,现有代码如下:
public static int GetPw(string filePath, double Freq, out double power, out string errMsg) { power = Double.MinValue; errMsg = "No error"; try { string[] lines = File.ReadAllLines(filePath); var filteredLines = lines.Where(line => !line.Contains("#") && !line.Contains("!") && !line.Contains("IND")); var splitLines = filteredLines.Select(line => line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); var validLines = splitLines.Where(parts => parts.Length >= 2); var sortedLines = validLines.OrderBy(parts => Math.Abs(float.Parse(parts[0]) - targetFrequency)); string closestPower = sortedLines.Select(parts => parts[1]).FirstOrDefault(); if (!Double.TryParse(closestPower, CultureInfo.InvariantCulture, out power)) { power = Double.MinValue; throw new InvalidCastException($"{power} is not a valid power value"); } } catch (Exception e) { errMsg=e.Message; return -1; } return 0; }
优化思路与实现
现有代码的核心问题是每次查询都要全量排序,仅能返回最近值且无法插值。针对10000行数据的规模,要保证高效,核心逻辑是一次性解析排序数据,利用二分查找快速定位插值区间,具体方案如下:
1. 预解析与缓存(多次查询必备)
如果函数会被多次调用,将解析排序后的数据缓存到静态集合中,避免重复读取文件和解析计算;单次查询可跳过缓存,但解析排序仍需执行。
2. 高精度解析与排序
- 用
double类型解析第一列数值,避免float的精度丢失问题 - 按第一列数值从小到大排序,为二分查找做准备
3. 二分查找定位区间
利用List<T>.BinarySearch快速定位目标值:
- 找到精确匹配时直接返回对应第二列数值
- 未找到时,通过返回的插入点计算出目标值所在的左右相邻数据点
4. 线性插值计算
找到左右相邻点(x1,y1)和(x2,y2)后,通过公式计算目标值x对应的y:y = y1 + (y2 - y1) * (x - x1) / (x2 - x1)
完整优化代码
using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; public static class DataQueryHelper { // 缓存解析排序后的数据,避免重复IO与计算(多次查询时启用) private static List<(double Frequency, double Power)> _cachedData; public static int GetPw(string filePath, double targetFreq, out double power, out string errMsg) { power = double.MinValue; errMsg = "No error"; try { // 加载并解析数据(未缓存时执行) _cachedData ??= File.ReadAllLines(filePath) .Where(line => !line.Contains("#") && !line.Contains("!") && !line.Contains("IND")) .Select(line => line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)) .Where(parts => parts.Length >= 2) .Select(parts => { if (double.TryParse(parts[0], NumberStyles.Float, CultureInfo.InvariantCulture, out double freq) && double.TryParse(parts[1], NumberStyles.Float, CultureInfo.InvariantCulture, out double pwr)) { return (Frequency: freq, Power: pwr); } return default; }) .Where(item => !double.IsNaN(item.Frequency)) .OrderBy(item => item.Frequency) .ToList(); if (_cachedData.Count == 0) { throw new InvalidOperationException("文件中无有效数据"); } // 处理边界情况:目标值小于最小频率 if (targetFreq <= _cachedData[0].Frequency) { power = _cachedData[0].Power; return 0; } // 目标值大于最大频率 if (targetFreq >= _cachedData[^1].Frequency) { power = _cachedData[^1].Power; return 0; } // 二分查找目标频率 int index = _cachedData.BinarySearch((targetFreq, 0), Comparer<(double, double)>.Create((a, b) => a.Item1.CompareTo(b.Item1))); if (index >= 0) { // 找到精确匹配 power = _cachedData[index].Power; return 0; } // 计算插入点,获取左右相邻数据 int insertIndex = ~index; var leftPoint = _cachedData[insertIndex - 1]; var rightPoint = _cachedData[insertIndex]; // 线性插值计算结果 power = leftPoint.Power + (rightPoint.Power - leftPoint.Power) * (targetFreq - leftPoint.Frequency) / (rightPoint.Frequency - leftPoint.Frequency); return 0; } catch (Exception e) { errMsg = e.Message; return -1; } } // 可选:清空缓存,用于文件更新后重新加载数据 public static void ClearCache() { _cachedData = null; } }
关键优化点说明
- 性能提升:解析排序仅执行一次(缓存后),后续查询用二分查找,时间复杂度从O(n log n)降至O(log n)
- 精度保障:全程使用
double类型处理数值,避免精度丢失 - 边界处理:针对目标值超出数据范围的场景,直接返回边界值,避免插值错误
- 容错性:过滤解析失败的行,避免无效数据干扰结果
内容的提问来源于stack exchange,提问作者Markus.H1
相关产品推荐
相关产品推荐

