在使用C语言转化公式进行计算时,易发现读取二维表格是一个较为困难的问题。尽管查找了相关资料,但未发现特定的解决方法。因此,作者自行开发了一套代码,以实现对二维表格数据的检索。
二维表格
编写公式时,我们可能需要使用一些经验数据来计算参数值,这就需要我们查表以获取相应的数值。例如某个公式中涉及的参数C,其值由角度θ和lg(A)决定,其中θ和面积A是已知量。
表1 C的经验公式
使用表格选取数据时,我们需要输入的两个量θ和lg(A),查表得到参数C的值。首先需要确定一种估算方法,因为实际情况下给出的角度不一定是整数,比如θ可能为32.15°,lg(A)可能为1.496。
由于在实际工况下给出的参数通常并不能精准匹配到表格中给出的参数值,因此在查表时需要对数据进行筛选和处理这里有两种较为常用的方法:
1.最近点优先原则: 采用欧氏距离度量给出的参数点与表格中给出的参数点之间的距离,选取与给出参数点最近点的数据点作为最终结果 ,例如给定的角度θ=32.15°处于30°和40°中间,相对更靠近30°,lg(A)=1.496处于lg(A)=1.4和lg(A)=1.5之间,相对更靠近1.5,根据距离的大小估计C的值最接近4.6,那么C=4.6,如下表所示,黄色区域的值。
2.加权平均原则: 根据欧氏距离筛选出与给出数据点最近的四个数据点,采用加权平均方法得到最终输出。权重的确定可采用距离的反比来确定。如下图所示,对黄色区域的四个数据进行加权平均。
由于上述两种方法为较为简单常用的数据筛选方法,实际情况下需要根据具体表数据类型来确定数据筛选与处理策略,在本文的情况下中直接采用最近点优先原则即可。
数据存储
解决估算的问题,就需要考虑以何种格式存储数据。当然如果处理表格较少,可以将数据写成容器或者其他形式存储在代码内,但是考虑到后期维护的方便性,建议使用Excel表格,或者使用txt文档存储数据,如果后期有对经验公式的数据进行修改,可以直接修改文档。本文使用txt文档存储数据,存储效果如下图所示。
数字之间用空格符号分隔,只存储了C的数据,并将txt文档命名为TableC。
代码实现步骤
确定了数据的估算方法和存储方法,接下来就需要考虑如何根据已知信息找到我们需要的数值,总共分为以下三步:
➤ 1、根据输入的角度θ、lg(A)、横轴数组和纵轴数组进行判断需要读取哪一行那一列的数据,输出读取数据所在行和所在列,使用的函数为GetNearestElementPosition()。
➤ 2、根据输入的txt文件路径,和所需数据所在行,输出txt文件中对应行的字符串,使用的函数为ReadLine()。
➤ 3、根据输入的字符串和所需数据所在列,输出对应列的数据,使用的函数为Extract()。
判断数据所在行列
首先需要根据输入的数据判断出需要读取的行和列。函数GetNearest()可以根据输入的目标数判断距离较近的值。函数GetNearestElementPosition()的作用是判断数组中与目标值差距最小的数的位置。
#include<iostream>
#include<fstream>
#include<math.h>
#include <sstream>
using namespace std;
/*
* @brief 判断目标数与传入参数距离
* @input 与目标数进行距离判断的两个数、目标值
* @return 距离目标数较近的数
* @author YuHaoHao(1813481317@qq.com)
* @date 2023-04-22
*/
double GetNearest(double x, double y, double target) {
if (target - x >= y - target)
return y;
else
return x;
}
/*
* @brief 寻找数组中与目标值差值最小的数的位置
* @input 数组、数组元素个数、目标值
* @return 数组中与目标数差值最小的数的位置
* @author YuHaoHao(1813481317@qq.com)
* @date 2023-04-22
*/
int GetNearestElementPosition(double arr[], int n, double target) {
double result = 0;
if (target <= arr[0])
{
result = arr[0];
}
if (target >= arr[n - 1])
{
result = arr[n - 1];
}
int left = 0, right = n, mid = 0;
while (left < right) {
mid = (left + right) / 2;
if (abs(arr[mid] - target) < 1e-8)
{
result = arr[mid];
}
if (target < arr[mid])
{
if (mid > 0 && target > arr[mid - 1])
{
result = GetNearest(arr[mid - 1], arr[mid], target);
}
right = mid;
}
else
{
if (mid < n - 1 && target < arr[mid + 1])
{
result = GetNearest(arr[mid], arr[mid + 1], target);
}
left = mid + 1;
}
}
int num = 0;
while (result != arr[num])
{
num++;
}
return num + 1;
}
检验一下是否可以正确地进行估算,测试函数如下,数组arry[]是转角θ的数据,数组包含的元素为10、20、30、40、50、60。假设输入的目标值为32.15°,那么根据最近判断,应该读取第三行的数据,上面GetNearestElementPosition函数的返回值因为3,测试代码如下所示。
int main()
{
double arry[6] = { 10,20,30,40,50,60 };
cout << "读取第" << GetNearestElementPosition(arry, 6, 32.15)
<< "行。" << endl;
system("pause");
return 0;
}
控制台输出结果如下:
根据输入的目标数就可以得到需要读取第几行第几列的数据。接下来需要根据需要打开txt文件读取数据。
读取指定行的数据
首先提取所需行的数据并以字符串的形式存储一行的内容,下面的函数CountLines()是用于计算文档中总共有多少行数据,ReadLine()是读取文件中指定行的数据。
/*
* @brief 判断文件中数据的行数
* @input 文件地址
* @return 文件中数据的行数
* @author YuHaoHao(1813481317@qq.com)
* @date 2023-04-22
*/
int CountLines(string filename)
{
ifstream ReadFile;
int n = 0;
string tmp;
ReadFile.open(filename.c_str());
if (ReadFile.fail())
{
return 0;
}
else
{
while (getline(ReadFile, tmp, '\n'))
{
n++;
}
ReadFile.close();
return n;
}
}
/*
* @brief 读取文件中所需行的数据
* @input 文件地址、所需数据在第几行
* @return string类型,文件第line行的字符串
* @author YuHaoHao(1813481317@qq.com)
* @date 2023-04-22
*/
string ReadLine(string filename, int line)
{
int lines, i = 0;
string temp;
fstream file;
file.open(filename.c_str());
lines = CountLines(filename);
if (line <= 0)
{
return "Error 1: 行数错误,不能为0或负数。";
}
if (file.fail())
{
return "Error 2: 文件不存在。";
}
if (line > lines)
{
return "Error 3: 行数超出文件长度。";
}
while (getline(file, temp) && i < line - 1)
{
i++;
}
file.close();
return temp;
}
验证一下输出内容的正确性,需要注意的是C++项目会有默认工作路径,默认工作路径通常为项目文件*.vcproi 所在路径,将文件放在项目配置的调试工作路径下,那么读取文件只要写文件名称即可,如下所示。
#include<iostream>
#include<fstream>
#include<math.h>
#include <sstream>
using namespace std;
int main()
{
double arry[6] = { 10,20,30,40,50,60 };
cout << "读取数据为:" << ReadLine("TableC.txt",
GetNearestElementPosition(arry, 6, 32.15)) << endl;
system("pause");
return 0;
}
输出结果:
提取指定列的数据
现在读取的是一行数据,并且这行数据是以字符串的形式存储,如何从字符串中找到我们需要的数并把他转化成double类型的数据,这是我们接下来的需要进行的工作。
函数Extract()的作用是从用空格分割的字符串中提取指定位置的数据,并把数据转化为double类型,具体代码如下
/*
* @brief 提取字符串中的数据
* @input 字符串、数据的位置
* @return double类型的数据
* @author YuHaoHao(1813481317@qq.com)
* @date 2023-04-22
*/
double Extract(string _chr, int _num)
{
string text = _chr;
size_t s = text.find_first_not_of(' ');
size_t e = text.find_last_not_of(' ');
stringstream ss(text.substr(s, e - s + 1));
double data;
double result[10];
int i = 0;
while (ss.good()) {
ss >> data;
result[i] = data;
i++;
}
return result[_num - 1];
}
测试代码如下,arry是角度数组,arrx是lg(A)的数组,已知角度θ=32.15°(Theta=32.15),lg(A)=1.496(log10A=1.496)。通过函数GetNearestElementPosition()确定数据所在行和列,使用ReadLine()函数读取txt文档中指定行的字符串,再通过函数Extract ()读取字符串中指定列的数据,最后打印输出结果。
int main()
{
double arry[6] = { 10,20,30,40,50,60 };
double arrx[9] = { 1,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8 };
int y = GetNearestElementPosition(arry, 6, 32.15);
int x = GetNearestElementPosition(arrx, 9, 1.496);
string char_ = ReadLine("data/TableC.txt", y);
double result = Extract(char_, x);
cout << "最终提取结果为:" << result << endl;
system("pause");
return 0;
}
输出结果如下:
整合函数
将上述步骤整合成一个函数FindNumber(),输入横轴参数、纵轴参数、横轴数组、纵轴数组、横轴数组个数、纵轴数组个数、读取文件名称,返回所需的参数。
/*
* @brief 提取经验表格中的指定数据
* @input 横轴参数、纵轴参数、横轴数组、
纵轴数组、横轴中数组个数、纵轴中数组个数、
读取的表格文件(.txt格式)
* @return double类型,查找到的经验数据
* @author YuHaoHao(1813481317@qq.com)
* @date 2023-04-22
*/
double FindNumber(double _x, double _y, double arrx[],
double arry[], int numx, int numy, string _chr)
{
double result = 0;
int x = GetNearestElementPosition(arrx, numx, _x);
int y = GetNearestElementPosition(arry, numy, _y);
string chr = ReadLine(_chr, y);
result = Extract(chr, x);
return result;
}
int main()
{
double arry[6] = { 10,20,30,40,50,60 };
double arrx[9] = { 1,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8 };
double log10C = FindNumber(1.496, 32.15, arrx, arry,
9, 6, "data/TableC.txt");
cout << "查表得到的结果为:" << log10C << endl;
double C = pow(10, log10C);
cout << "参数C为:" << C << endl;
system("pause");
return 0;
}
输出结果如下:
本文针对的是基于C语言进行公式转化时经常会遇到的二维数据索引问题,感谢哈工程(青岛)船舶工软团队余昊昊同学提供的解决思路,希望能够给您提供帮助!