I have a text file structured like this:
6
0,2,0,0,5,0
3,0,4,0,0,0
0,0,0,6,1,0
0,0,0,0,2,0
0,0,0,0,2,4
1,0,0,0,0,0
0,5
The first number represents the number of vertices and the 2D matrix is an adjacency matrix. The last line contains start and end points. I understand how to use the data and what its purpose is; what I'm struggling with is properly reading the data so I can process it. I currently have this:
void directedGraphAnalysis() {
//Open file, read contents, write to graph
ifstream file("test2a.txt");
string data = ""; //Each line will be read as a string and converted to an int
int nodeCount = 0;
int* matrix = nullptr;
int matrixIndex = 0;
int count = 0;
int v1 = INT_MAX;
int v2 = INT_MAX;
while (getline(file, data, ',')){
int x = stoi(data);
count;
if (count == 1) {
nodeCount = x;
matrix = new int[nodeCount * nodeCount];
}
else if (count < (nodeCount * nodeCount) - 1) {
matrix[matrixIndex ] = x;
}
else if (v1 == INT_MAX) {
v1 = x;
}
else {
v2 = x;
}
cout << "X: " << x << endl;
}
//DirectedGraph graph = DirectedGraph(matrix, nodeCount);
//graph.displayGraph();
cout << "ANALYSIS COMPLETE." << endl;
}
This implementation seems to get the node count right but every line after that skips the first digit, meaning v1 and v2 are never set and the matrix has incorrect edges that don't match with the text file. Matrix is simply a 1D array representing a 2D matrix of size nodeCount * nodeCount. I'm not sure what it is I'm doing that's causing the matrix to be improperly filled, nor do I know why the first digits are being skipped.
CodePudding user response:
Because std::getline
only accepts a single character as delimiter.
If you use ,
as delimiter, the NEW LINE(0x0D, 0x0A)
is normal character.
For example, before the first ,
character, std::getline
read:
6
0
and stoi convert it to
6
then the first digit of next line is missing.
And I try to fix the logic as below:
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
using namespace std;
void directedGraphAnalysis() {
//Open file, read contents, write to graph
ifstream file("test2a.txt");
string data = ""; //Each line will be read as a string and converted to an int
string line = "";
int nodeCount = 0;
int* matrix = nullptr;
int matrixIndex = 0;
int count = 0;
int v1 = INT_MAX;
int v2 = INT_MAX;
while (getline(file, line)){
stringstream stream(line);
while (getline(stream, data, ',')){
int x = stoi(data);
count;
if (count == 1) {
nodeCount = x;
matrix = new int[nodeCount * nodeCount];
}
else if (count < (nodeCount * nodeCount) - 1) {
matrix[matrixIndex ] = x;
}
else if (v1 == INT_MAX) {
v1 = x;
}
else {
v2 = x;
}
cout << "X: " << x << endl;
}
}
//DirectedGraph graph = DirectedGraph(matrix, nodeCount);
//graph.displayGraph();
cout << "ANALYSIS COMPLETE." << endl;
}
int main()
{
directedGraphAnalysis();
}
CodePudding user response:
You need to analyze your source file and then do input operations as needed.
The adjency matrix seems to be for a directed and weighted graph. It is not clear to me, if it is row or column oriented, but this is not important for the reding operation.
So, would do we see?
- The dimension as an unsigned interger number on its own line
- It follows a rectangular matrix with the above given dimension. The rows are given as comma separated values. This, we call CSV. And, there are many many ways to split a csv into its components: BecaUse the matrix is quadratic, there will be dimension rows.
- At the end, we have and additionalline, containing 2 values, the start coordinates, separated by a comma.
Deriving from that observations, we will decide how to read.
The first line can be simply read, with a standard formatted input function, using the extractor operator >>. Formatted input
will alwaystry toconvert the input data, until it sees a white space (including the newline character '\n'). It will not consume the white space, so the '\n').
The next lines could be written as a whole line using the std::getline
(unformatted input) function. But caveat. std::getline
will read, until it finds a newline '\n'. And remember from above, there is still a newline character in theinput stream. So,if you would simply call std::getline
, it would read an empty string and then consume the new line character. To solve this, we can simply use the std::ws function. This will consume, but not read, all white spaces in front of a text. So, we can always write: std::getline(file >> std::ws, data);
This will first call file >> std::ws
and then invoke std::getline
.
Ok, this theory should be clear now.
Then, we need to create a 2 dimensional array. You are just creating a one dimensional array. This can be done by first creating an array for the rows, and then, for each row, additional arrays for the columns. This wecould do like the below:
// allocate memory for rows
int** matrix = new int* [dimension];
// For each row, allocate memory for the columns
for(unsigned int row=0; row < dimension; row)
matrix[row] = new int [dimension];
Now we have a 2d array, which we can address with matrix[row][col];
Next, because we do know the dimension, we can create a loop and call std::getline
dimension times.
After having read one line, we need to split that line and store the values. For that we will use one common approach, by using an std::istringstream
and std::getline
until we see a comma.
At the end, we read the last line and extract the start coordinates in the same way.
For every read operation an error check should be added.
Then an example program could looklike below.
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
int main() {
// Open the file
std::ifstream fileStream{ "r:\\text2a.txt" };
// Check, if we could open the file
if (fileStream) {
// Read the dimension of the matrix and check for valid input
unsigned int dimension{};
if ((fileStream >> dimension) and (dimension > 0)) {
// Now we haveopened the file and read the dimension
// Allocate array. Firs,allocate memory for rows
int** matrix = new int* [dimension];
// And now, for each row, allocate space for columns
// Then read the rows with the column values
for (unsigned int row = 0; row < dimension; row) {
matrix[row] = new int[dimension] {};
// Read next line from file
std::string line{};
if (std::getline(fileStream >> std::ws, line)) {
// Put the line into an std::istringstream, so that we can extract the column values
std::istringstream iss(line);
// Extract all columns
for (unsigned int col = 0; col < dimension; col) {
std::string valueAsString{};
if (std::getline(iss, valueAsString, ',')) {
matrix[row][col] = std::stoi(valueAsString);
}
else {
// Could not read column value. Show error message
std::cerr << "\n\n*** Error. Could not read value at row " << row << ", column: "<< col << "\n\n";
}
}
}
else {
// Could not read row with column values
std::cerr << "\n\n*** Error. Could not read row number " << row << "\n\n";
}
}
// Now we have read the matrix.
// Get the start point
unsigned int rowStart{}, columnStart{};
char comma{};
if ((fileStream >> rowStart >> comma >> columnStart) and comma == ',') {
// Now, we have all data
// Show some debug output
std::cout << "Adjancency matrix\n\nDimension: " << dimension << "\n\nMatrix:\n\n";
for (unsigned int row = 0; row < dimension; row) {
for (unsigned int col = 0; col < dimension; col) {
std::cout << matrix[row][col] << ' ';
}
std::cout << '\n';
}
std::cout << "\n\nStart point: " << rowStart << ", " << columnStart << '\n';
// Release all allocated memory
for (unsigned int row = 0; row < dimension; row)
delete[] matrix[row];
delete[] matrix;
}
else {
// Could not read start values
std::cerr << "\n\nError: Could not read start values\n\n";
}
}
else {
// Invalid input for dimension of adjacency matrix. Show error message
std::cerr << "\n\n*** Error. Invalid dimentsion found\n\n";
}
}
else {
// File could not be opened. Show error message
std::cerr << "\n\n*** Error. Could not open source file\n\n";
}
}