C++读取txt文本中的矩阵数据

【问题描述】

机器学习中,常需要处理输入数据为矩阵的txt文档,形如:

1 3 3
-1 1 1
1 4 3

一行表示一个样本,Xi为D维向量,i=1,2,...N,Yi取值{+1,-1}。其中,不同样本间用换行隔开;第一列表示数据标签Yi,后面几列表示数据的各维度取值Xi。

需要实现程序,将txt中的文件读取到数组X和Y中,其中X为N*D维数组,Y为N维数组。

【主要知识点】

文件读取、getline、peek

【问题实现】

主要步骤:

获取行数N

获取列数,-1得到数据的维度D

读入数据

1、获取行数

获取行数时,需要快速找到每行的末尾'\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 ;
}
}


2、获取列数

获取列数时(假设数据每行的列数相同,只计算第一行的列数作为整个矩阵的列数,至于后续列数是否相符,在具体读入数据时判断),需要取其中一行,逐个读取数值,到行尾‘\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;
}

3、获取数据

// =============== 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



【总结】

以上只是考虑输入数据的几种异常情况做的处理,实际中还有其他情况,如中间有空行等等,未全面考虑。

你可能感兴趣的:(C++基础)