txt file to vector giving wrong values

I'm loading a txt file into a vector using the code I've written below. It works great except for one BIG issue i'm having. First I'll give a very small example of a text file being converted.

12,26,1,3-4-71,13-19
21,56,8,12-9-35-7,16
There are more lines and more numbers per line, but kept example small.

Now, the BIG issue I don't know how to solve is: I get negative values in the vector, due to the "-" being present in the text file. I would like to keep the code as simple and fast as possible. I could eventually be using a txt file with 100,000+ lines. I want the " - " to be treated as a space. In the first line not -4 just 4, not a -19 just 19. In the vector they are showing up as negative numbers. Hope thats clear enough example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    std::ifstream infile;
    std::string wpfile;
    
    std::cout << "Enter the name of your Properties File : ";
    std::cin >> wpfile;
    
    infile.open(wpfile);
    if (!infile) {
        std::cerr << "Problem opening file" << std::endl;
    }

    int numberoflines {0};
    while (std::getline(infile, line))
        ++numberoflines;
    std::cout << "Your Properties File has " << numberoflines << " lines" << std::endl;
    infile.clear();
    infile.seekg(0);
    std::cout << "\n  Loading Your File.....\n";
    
    std::vector<std::vector<int>> wp (numberoflines, std::vector<int>(65));

// Somewhere below here is where I think the change can be made   
 
    while (!infile.eof()) {
        for (int i {0}; i < numberoflines ; ++i) {    
            for (int j {0}; j < 65; ++j) {
                infile >> wp[i][j];
            }
        }
    }
    infile.close();
    
Last edited on
To read a list of - separated values:

* If you read each char sequence between the commas into strings and turn them into std::istringstream you could use std::getline(stream, numberStr, '-') to extract each number (as a string) and then you could use std::istringstream again or std::stoi to convert numberStr into an integer.

* Another alternative, if commas and minus signs should be treated the same, is to replace the commas and/or minus signs in the line string using std::replace so that they become the same character. Then you could turn it into a stream and easily extract each number using getline (if you turn them into spaces you could use >> directly).
Last edited on
Why does the file have both ',' and '-' as separators?
You code trusts that every line has 65 numbers. (Your example did not.)


PS. Could one std::ignore on line 13? One does not use the value.
This could be done by using <regex> to split the lines 😎

In the following example, the regex matches any sequence of digits (i.e. 0 to 9 characters), while anything in between is ignored:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <regex>
#include <string>
#include <fstream>

int main()
{
    const std::regex rgx("\\d+"); // <-- matches any sequence of "digit" characters
    const std::sregex_token_iterator end;

    std::ifstream infile;
    infile.open("C:\\Temp\\test.txt");
    if (!infile) {
        std::cerr << "Problem opening file!" << std::endl;
    }

    std::vector<std::vector<int>> values;

    std::string line;
    while (std::getline(infile, line)) {
        std::cout << "line str: \"" << line << '"' << std::endl;
        if (values.empty() || (!values.back().empty())) {
            values.emplace_back();
        }
        for (std::sregex_token_iterator iter(line.begin(), line.end(), rgx); iter != end; ++iter) {
            if (iter->length() > 0) {
                std::cout << "line tok: \"" << *iter << '"' << std::endl;
                values.back().push_back(std::stoi(*iter));
            }
        }
    }
}
Output:
line str: "12,26,1,3-4-71,13-19"
line tok: "12"
line tok: "26"
line tok: "1"
line tok: "3"
line tok: "4"
line tok: "71"
line tok: "13"
line tok: "19"
line str: "21,56,8,12-9-35-7,16"
line tok: "21"
line tok: "56"
line tok: "8"
line tok: "12"
line tok: "9"
line tok: "35"
line tok: "7"
line tok: "16"
line str: "12 91;87#21"
line tok: "12"
line tok: "91"
line tok: "87"
line tok: "21"

____


Also, why do you read (parse) the entire file twice? There is no need to read every line just to determine the total number of lines! Apparently you do this to allocate the std::vector and then you re-start from the beginning in order to fill that vector. Instead, you can simply append your values to the std::vector as they come in; the vector grows as needed! This way, the file can be parsed using a single pass.
Last edited on
Using a function to split a string at multiple delimiters, consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <fstream>
#include <cstdlib>

std::vector<int> split(const std::string& inputString, std::string_view delimiters) {
	std::vector<int> result;
	size_t startPos {};

	for (size_t endPos {}; (endPos = inputString.find_first_of(delimiters, startPos)) != std::string::npos; startPos = endPos + 1)
		if (endPos != startPos)
			result.push_back(std::strtol(inputString.substr(startPos, endPos - startPos).c_str(), nullptr, 10));

	if (startPos != inputString.length())
		result.push_back(std::strtol(inputString.substr(startPos).c_str(), nullptr, 10));

	return result;
}

int main() {
	constexpr std::string_view delimiters {",-"};

	const std::string wpfile {"properties.txt"};
	std::fstream infile {wpfile};

	if (!infile.is_open())
		return (std::cout << "Cannot open file '" << wpfile << "'\n"), 1;

	std::vector<std::vector<int>> wp;

	std::cout << "\nLoading Your File.....\n";

	for (std::string line; std::getline(infile, line); wp.emplace_back(split(line, delimiters)));

	std::cout << "Your Properties File has " << wp.size() << " lines\n\n";

	for (const auto& props : wp) {
		for (const auto& p : props)
			std::cout << p << ' ';

		std::cout << '\n';
	}
}



Loading Your File.....
Your Properties File has 2 lines

12 26 1 3 4 71 13 19
21 56 8 12 9 35 7 16

Thanks to everyone who gave replies to my question. Guess I'm way over my head with this one. I'm very new to the learning in c++. I'm just kind of a hobbyist, learning my way online through udemy. Don't really grasp any of the answers that were given. I mean no disrespect to anyone, just clueless myself.

The output that seeplus got was exactly what I was hoping to get. That looks exactly what i want loaded into the vector. My apologies, I just don't understand the code. Maybe i should just play around with a single program to replace the commas and - with a "space" from a txt file then use that text file. Sorry, just sounds easier to do.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <string>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <vector>
#include <sstream>
#include <iostream>

int main()
{
  // 1. make a stream which gets its data from the file
  std::ifstream input_file_stream{ "test_data.txt" }; 
  
  // 2. read the entire file into the string file_contents
  std::string file_contents(
      std::istreambuf_iterator<char>{input_file_stream}, {});
      
  // 3. replace commas with spaces in the file_contents:
  std::replace(std::begin(file_contents), std::end(file_contents), ',', ' ');
  
  // 4. replace dashes with spaces in the file_contents
  std::replace(std::begin(file_contents), std::end(file_contents), '-', ' ');
  
  // At this point, file_contents contains a copy of the text in the file,
  // without any spaces or dashes within it.
  
  // 5. make a stream which gets its data from the file_contents string:
  std::istringstream file_contents_stream {file_contents};
  
  // 6. Treat file_contents_stream exactly like you would input_file_stream.
  std::vector<std::vector<int>> numbers_in_file;
  for (std::string line; std::getline(file_contents_stream, line); )
  {
    std::istringstream line_stream{line};
    std::vector<int> numbers_in_line;
    for (int n; line_stream >> n; )
      numbers_in_line.push_back(n);
      
    numbers_in_file.push_back(numbers_in_line);

    // If you're feeling fancy, use this as the loop body:
    // std::istringstream line_stream{line};
    // numbers_in_file.emplace_back(
    //     std::istream_iterator<int>{line_stream}, 
    //     std::istream_iterator<int>{});
  }
  
  std::cout << "your file contains " << numbers_in_file.size() << " lines\n";
  std::cout << "the numbers are as follows:\n";
  for (std::size_t line = 0; line < numbers_in_file.size(); ++line)
  {
    for (std::size_t i = 0; i < numbers_in_file[line].size(); ++i)
      std::cout << numbers_in_file[line][i] << ' ';
    std::cout << '\n';
  }
}


Demo:
https://coliru.stacked-crooked.com/a/c3291d538c39f1e2
Last edited on
Another approach could be, read all the numbers as in the original post, but make another pass through the vector to multiply all the negative numbers by -1.
Last edited on
My code from above can be simplified as strtol() terminates on a non-digit char and can return a pointer to this position. Consider where any non-digit can be used as a separator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <cstdlib>

std::vector<int> split(const std::string& inp) {
	std::vector<int> res;

	for (char *pos { (char*)inp.c_str() }, *end {}; *pos; pos = end + (*end != 0))
		res.push_back(std::strtol(pos, &end, 10));

	return res;
}

int main() {
	const std::string wpfile {"properties.txt"};
	std::fstream infile {wpfile};

	if (!infile.is_open())
		return (std::cout << "Cannot open file '" << wpfile << "'\n"), 1;

	std::vector<std::vector<int>> wp;

	std::cout << "\nLoading Your File.....\n";

	for (std::string line; std::getline(infile, line); wp.emplace_back(split(line)));

	std::cout << "Your Properties File has " << wp.size() << " lines\n\n";

	for (const auto& props : wp) {
		for (const auto& p : props)
			std::cout << p << ' ';

		std::cout << '\n';
	}
}



Loading Your File.....
Your Properties File has 2 lines

12 26 1 3 4 71 13 19
21 56 8 12 9 35 7 16


@Brian845. If you don't understand something, just ask and we'll provide an explanation.
I really like mbozzi's suggestion, but if you want to read the numbers and skip the minus signs, here's a simple way using your original code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <fstream>
#include <iostream>
#include <vector>

int main()
{
    std::ifstream infile;
    std::string wpfile;
    std::string line;
    
    std::cout << "Enter the name of your Properties File : ";
    std::cin >> wpfile;
    
    infile.open(wpfile);
    if (!infile) {
        std::cerr << "Problem opening file" << std::endl;
    }

    int numberoflines {0};
    while (std::getline(infile, line))
        ++numberoflines;
    std::cout << "Your Properties File has " << numberoflines << " lines" << std::endl;
    infile.clear();
    infile.seekg(0);
    std::cout << "\n  Loading Your File.....\n";
    
    std::vector<std::vector<int>> wp (numberoflines, std::vector<int>(65));

// Somewhere below here is where I think the change can be made   
 
    while (infile) {		// check for end of file or error
        for (int i {0}; i < numberoflines ; ++i) {    
            for (int j {0}; j < 65; ++j) {
	    // skip commas, whitespace and minus sign
		while (infile >> std::ws && (infile.peek() == '-' || infile.peek() == ',')) {
		    infile.get();
		}
		// read the next number
		infile >> wp[i][j];
            }
        }
    }
    infile.close();

    // Print the results
    for (auto &lineVec : wp) {
	for (auto & num : lineVec) {
	    std::cout << num << ' ';
	}
	std::cout << '\n';
    }
}
I was able to spend some time this evening to try the suggestions out.

Since I'm really green, I was able to follow/understand dhaydens code a little easier than others. NO DISRESPECT to anyone who replied. I'm just very new at this. Anyway, it worked as I needed it to. I did printout the other suggestions for further reference and understanding of the code. Probably when I get further along in the course on udemy, I will look back at those code printouts and say "UGH! Now I get what that code means."

THANKS to everyone!!
This board has always helped in the past. You guys are great!
There's a good (and free) set of C++ tutorials and lessons available online: Learn C++

https://www.learncpp.com/

You won't learn most of the newer C++ stuff, C++20/C++23, though. The site does give a lot of example code snippets to try along with the lessons.

One thing to know and remember: each new standard can change how to do some tasks. Parsing text for example.

The various code snippets being provided show how things can change with each standard.

C++20 and C++23 really made some major mods to the language, modules instead of headers and formatted output.

There's a great online C/C++ reference site, though it is not something for learning C++. Especially for a beginner.

https://en.cppreference.com/w/

Topic archived. No new replies allowed.