机器学习中,常需要处理输入数据为矩阵的txt文档,形如:
1 3 3
-1 1 1
1 4 3
一行表示一个样本
需要实现程序,将txt中的文件读取到数组X和Y中,其中X为N*D维数组,Y为N维数组。
文件读取、getline、peek
获取行数N
获取列数,-1得到数据的维度D
读入数据
获取行数时,需要快速找到每行的末尾'\n',计数器+1,可直接用getline(fileStream,tmp,'\n'))
// ================= Funtion: getFileRows ======================================
// =========== output :行数 ====================================================
// 逐行读取文件计数,得到输入文件的行数
int getFileRows(const char *fileName){
ifstream fileStream;
string tmp;
int count = 0;// 行数计数器
fileStream.open(fileName,ios::in);//ios::in 表示以只读的方式读取文件
if(fileStream.fail())//文件打开失败:返回0
{
return 0;
}
else//文件存在
{
while(getline(fileStream,tmp,'\n'))//读取一行
{
if (tmp.size() > 0 )
count++;
}
fileStream.close();
return count ;
}
}
获取列数时(假设数据每行的列数相同,只计算第一行的列数作为整个矩阵的列数,至于后续列数是否相符,在具体读入数据时判断),需要取其中一行,逐个读取数值,到行尾‘\n’时结束。
思路:(1)从文件第一行第一列开始,按空格自动分离出数值,(2)每读一个,观察该位置的下一个字符,如果是行尾'\n',停止,如果不是,继续。
实现:(1)逐个读取方法: fileStream >> tmp,可自动略去空格和换行来读取,但也有注意事项要处理(见异常数据处理3);
(2)每读取一次,指针就向后移动,用fileStream.peek()可以只看指针指向的当前字符,仅观测,不移动指针位置(get()会移动指针位置);
如第一行“1 3 3”,刚打开文件时,指针指向第一个元素1,c='1';
执行一次 fileStream >> tmp后,读入'1',指针移到1后面的空格(注意,不是移到空格后的'3'),c = fileStream.peek()结果为' ';
再执行一次 fileStream >> tmp后,指针先跳过当前的空格(多个空格也会跳过,空格加换行也一并跳过),读入'3'到tmp中,指针移到'3'后面的空格,c = fileStream.peek()结果为' ';
当最后一个元素‘3’被 fileStream >> tmp读取后,指针就指向行尾'\n',所以就可以中止while循环。
// ================= Funtion: getFileColumns ===================================
// =========== output :列数 ====================================================
int getFileColumns(const char * fileName){
ifstream fileStream ;
fileStream.open(fileName,std::ios::_Nocreate);
double tmp = 0;
int count = 0; // 列数计数器
char c; //当前位置的字符
c = fileStream.peek();
while (('\n' != c) && (! fileStream.eof())) // 指针指向的当前字符,仅观测,不移动指针位置
{
fileStream >> tmp;
++count;
c = fileStream.peek();
}
fileStream.close();
return count;
}
// =============== FUNCTION: getInputData =========================================
// === 文件fileName 已经经过数据检查,保证是正常数据
// === 输出为0,表示异常中止。
int getInputData(const char *fileName, int rowNum, int colNum, double *X, double *Y)
{
ifstream fileStream;
string oneLine = ""; //输入文件的某一行
double tmp = 0; //当前位置上的数值
int rowCount; // 行数计数器
int colCount =0; // 列数计数器
int index =0; // 当前位置在X[]数组的下标
int maxIndex = rowNum * (colNum -1) - 1;
// 打开文件
fileStream.open(fileName,ios::in); //ios::in 表示以只读的方式读取文件
if(fileStream.fail())//文件打开失败:返回0
{
cout << "文件不存在." << endl;
fileStream.close();
system("pause");
return 0;
}
else//文件存在
{
// 读入数据
rowCount =0; //初始化当前行数为0
colCount = 0; //当前列数清零
double tmp = 0; //当前读入的数值
while (!fileStream.eof()){
fileStream >> tmp;
if (colCount == 0){ //第一列,是标签Y
if (rowCount < rowNum){ //越界检查
Y[rowCount] = tmp;
//cout << Y[rowCount] << endl;
}
else{
cout << "计算出的行数与输入数据不符,请检查数据(如文件末尾不能有空行)." << endl;
fileStream.close();
system("pause");
return 0;
}
}
else{ //非第一列,是数据X
index = rowCount * (colNum -1) + colCount -1;
if (index <= maxIndex){ //越界检查
X[index] = tmp;
//cout << X[index] << endl;
}
else{
cout << "X[]下标超出数组范围,可能是文件中某行的列数>第一行的列数,退出!" << endl;
fileStream.close();
system("pause");
return 0;
}
}
if ('\n' != fileStream.peek()){ // 未到行尾,colCount累加,rowCount不变
++colCount;
}
else{ //已到行尾,colCount清零,rowCount累加
if ((colCount + 1) != colNum) //越界检查
{
cout << "第" << rowCount + 1 <<"行,输入的列数与文件列数不一致,请检查数据." << endl;
fileStream.close();
system("pause");
return 0;
}
else{
rowCount++; // 换下一行
colCount = 0; // 列数清零
}
}
}
// 关闭文件
fileStream.close();
return 1 ;
}
}// END OF getInputData
1、文件为空:除了判断当前位置是否为'\n',外,还需判断文件是否结束fileStream.eof():
2、最后一行数据是否要加个回车换行:看程序中是否有加判断,本程序设计中,计算行数有跳过空行,而读入数据时没有,故会在数据读入时发现行数不对,异常退出。
3、行尾是空格:因为“>>”会自动跳过空格和换行,所以以下情况,会出错,将下一行数据也读入第一行,列数变为6。——程序中未处理,需事先检查。
'1 3 3 '
'-1 1 1'
4、只有1列
5、第一行列数和后面的列数不符
6、行数
当遇到文件末尾有空行时,如下,fileStream >> tmp,读取到3后,再执行ileStream >> tmp,tmp值仍一直是'3':
3
以上只是考虑输入数据的几种异常情况做的处理,实际中还有其他情况,如中间有空行等等,未全面考虑。