Reading text files into 2D arrays

This an adaptation of a program used earlier within another thread. The original code was designed to read individual words into an array of strings, and works fine.

This code is attempting to read the same words into a 2D array, maintaining the structure of the file. However, it doesn't work properly.

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
  #include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;

int main()
{
    string testline;
    string word[6][3];

    ifstream Test ( "Data.txt" );

    if (!Test)
    {
        cout << "There was an error opening the file.\n";
        return 0;
    }
    //store words in array
    int x=0,y=0;
    while( Test>>testline )
    {
        word[y][x]=testline;
        x++;
        if (testline=="")
        y++;
    }
    //output whole array with array position numbers for each entry
    cout<<"Array contents:\n";
        for (int y=0;y<6;y++)
        {
            for (int x=0;x<3;x++)
            cout<<word[y][x]<<"("<<y<<","<<x<<")"<<endl;
        }
    return 0;
}

My input file:
Steven Seagal
1234 Post Drive
Ventura, CA 90734

Adam Sandler
356 Golf Street
Calabasas, CA 92136

The check on line 25 is failing to increment the vertical array counter when applicable. How can I get my >> operator to detect the end of a line? Also, how can I get the third position in each row to remain blank in the event the file only contains two strings on a line (such as their names)? My output looks like this:
Array contents:
Steven(0,0)
Seagal(0,1)
1234(0,2)
Post(1,0)
Drive(1,1)
Ventura,(1,2)
CA(2,0)
90734(2,1)
Adam(2,2)
Sandler(3,0)
356(3,1)
Golf(3,2)
Street(4,0)
Calabasas,(4,1)
CA(4,2)
92136(5,0)
(5,1)
(5,2)
when it should look like this:
Array contents:
Steven(0,0)
Seagal(0,1)
(0,2)
1234(1,0)
Post(1,1)
Drive(1,2)
Ventura,(2,0)
CA(2,1)
90734(2,2)
Adam(3,0)
Sandler(3,1)
(3,2)
356(4,0)
Golf(4,1)
Street(4,2)
Calabasas,(5,0)
CA(5,1)
92136(5,2)

Also, how can I place two output windows such as this side by side for neater posting in the future?
Last edited on
How can I get my >> operator to detect the end of a line?


Use the right tool for the job. Use std::getline to extract a line, and break the string apart from there. You may wish to use a std::istringstream to help you parse the line.

Using a vector of vectors is a better idea than using a two dimensional array here. If an address looks like the following:
356 Golf Drive, Bldg A Ste 17
San Jose, CA 95101

you will access outside the bounds of your array.

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
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

std::istringstream in(
	"Steven Seagal\n"
	"1234 Post Drive\n"
	"Ventura, CA 90734\n"
	"\n"
	"Adam Sandler\n"
	"356 Golf Street\n"
	"Calabasas, CA 92136\n"
);

int main()
{
	std::vector<std::vector<std::string>> lines ;
	
	std::string line ;
	while (std::getline(in, line))
	{
		std::vector<std::string> tokens ;
		std::istringstream ls(line) ;
		std::string token ;
		
		while ( ls >> token )
			tokens.push_back(token) ;
			
		if ( tokens.size() )		// skip empty lines.
			lines.emplace_back(std::move(tokens)) ;
	}
	
	for ( auto& line : lines )
	{
		for ( auto& token : line )
			std::cout << token << ' ' ;
		std::cout << '\n';
	}
}


http://ideone.com/j36Okr

Something similar may be done for your 2d array.
You could use getline to have each line stored in a string. Then use stringstreams to hold each line. Then once you are done processing a line put the '\n' into the array and increment.

Something like:
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
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

int main(int argc, char** argv)
{
    std::fstream fin("example.txt");
    std::vector<std::string> words;
    std::string line;
    while(fin && getline(fin, line))
    {
        std::string word;
        std::stringstream ss(line);
        while(ss && ss >> word)
        {
            std::cout << word << "\n";
            words.push_back(word);
        }
        std::cout << "(newline)\n";
        words.push_back("\n");
    }
    return 0;
}
Steven
Seagal
(newline)
1234
Post
Drive
(newline)
Ventura,
CA
90734
(newline)
(newline)
Adam
Sandler
(newline)
356
Golf
Street
(newline)
Calabasas,
CA
92136
(newline)


Edit: Looks like I had a similar idea, but cire beat me to it LOL.
Last edited on
This explains a lot about the program behavior, but I am fairly new to vectors and more comfortable with 2D arrays. Afterwards, I intended to adapt it to work with a 2D vector, as of course the fact that addresses may contain more than 3 fields is relevant. How do the &auto based loops work, I haven't seen that before?
Last edited on
This explains a lot about the program behavior, but both examples make use of one dimensional vectors.


They both do. Mine also makes use of a two dimensional vector. See line 18.
I see that now, cire, and that's why I edited my comment. I'm still gaining familiarity with 2D vectors and how they work, though. I'm still curious about the &auto based loop.
How do the &auto based loops work, I haven't seen that before?

It is basically saying for each x that is in the list of xs.
1
2
3
4
5
6
int foobar[10] = {1,2,3,4,5,6,7,8,9,0};

for(auto& x : foobar)
{
    cout << x << ',';
}
1,2,3,4,5,6,7,8,9,0,


'foobar' can be any STL container or normal array (or I believe any custom type... as long as your type provides begin() and end() methods).

'x' is a reference to each element in that container.

It's functionally the same as this:

1
2
3
4
5
6
for(int i = 0; i < 10; ++i)
{
    auto& x = foobar[i];

    cout << x << ',';
}


The only difference is:

- you don't need to explicitly state the size of the array/container in the for loop
- it works and has the same syntax regardless of the type of container
Interesting, I can see the potential uses for this. Thanks for the clarification. After adapting cire's earlier example, I cannot get the compiler to recognize emplace_back. How do you get your output and code windows side by side? This would make several of my posts easier to read through.
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
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
using namespace std;

int main()
{
    string line;
    vector<vector<string> > lines;

    ifstream Test ( "Data.txt" );

    if (!Test)
    {
        cout << "There was an error opening the file.\n";
        return 1;
    }
    else
    {
        //store words in vector
        while(getline(Test,line));
        {
            vector<string> tokens;
            string token;
            while (Test>>token)
                tokens.push_back(token);

            if (tokens.size())
                lines.emplace_back(move(tokens));
        }
        //output vector
        for (auto& line : lines)
        {
            for (auto& token : line)
                cout<<token<<" ";
            cout<<endl;
        }
    return 0;
    }
}

Line 31 - error: 'class std::vector<std::vector<std::basic_string<char> > >' has no member named 'emplace_back'
Line 31 - error: 'move' was not declared in this scope
Line 34 & 36 - error: range-based 'for' loops are not allowed in C++98 mode
Line 34 - error: ISO C++ forbids declaration of 'line' with no type [-fpermissive]
Line 36 - error: ISO C++ forbids declaration of 'token' with no type [-fpermissive]

Yet I've declared both line and token as strings.

I also received a warning in addition to the above errors:
Line 34 & 36 - warning: 'auto' changes meaning in C++11; please remove it [-Wc++0x-compat]

Just so you guys know, I'm using Code::Blocks 12.11.
Last edited on
Looks like you're compiling as C++98 and not C++11.

To enable C++11 support in C::B... I googled it and found this thread:

http://en.sfml-dev.org/forums/index.php?topic=8860.0
Oddly, if I add std:: in front of move or some of the other commands, I receive an additional error like below.
Line 31 - error: 'move' is not a member of 'std'

Thanks, Disch! I missed that comment earlier. After going into the settings and changing that, it now compiles without errors, but I receive no output, and didn't know why at first. However, attempting to output lines[0][0] results in a program crash, so it's apparently not adding anything to the vector. I must have overlooked something fundamental, but by placing a cout statement within the getline based while loop I determined it is only executing once.

I tried multiple changes, such as if (tokens.size()==0), but can't pinpoint the problem. I suppose I'm more comfortable with arrays, but getting it working with either method would be fine.
Last edited on
Ok, further changes yielded better results, but there are still some major issues. By setting up stringstream, it now seems to be grabbing words, yet my output is only the last 3.
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
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
using namespace std;

int main()
{
    string line;
    vector<vector<string> > lines;

    ifstream Test ( "Data.txt" );

    if (!Test)
    {
        cout << "There was an error opening the file.\n";
        return 1;
    }
    else
    {
        //store words from input file in vector
        while(getline(Test,line));
        {
            vector<string> tokens;
            stringstream ls(line);
            string token;
            while (ls>>token)
                tokens.push_back(token);

            if (tokens.size())
                lines.emplace_back(move(tokens));
        }
        //output vector
        for (auto& line : lines)
        {
            for (auto& token : line)
                cout<<token<<" ";
            cout<<endl;
        }
     return 0;
    }
}
Calabasas, CA 92136

Needless to say, only the last three words were stored. What can I do to improve on this? Including a cout statement within the inner while loop demonstrated that it is only reading in those three words, and the outer while loop is only executing once. Why is that?
Last edited on
I got it! It was so silly in the end, the semicolon at the end of line 23 was causing all my headaches! And it skipped the blank line just like I wanted it to!

One last question: when populating the vector, how easy would it be to add blank strings ("") for the third position when no third string exists?

Last edited on
Just push_back("");
Thanks, kevinkjt2000. I know that will work, but how do I get the vector populating loop to recognize the correct circumstances to apply it? I went about it this way, but the downside is in the event any address has more than 3 fields it becomes pointless. How can I apply the same concept regardless of the number of fields involved? Considering that it may encounter addresses that have more fields after processing earlier addresses, is it even possible?
1
2
3
4
5
6
7
8
9
10
11
12
while(getline(Test,line))
        {
            vector<string> tokens;
            stringstream ls(line);
            string token;
            while (ls>>token)
                tokens.push_back(token);
            if (tokens.size()<3)
                tokens.push_back("");
            if (tokens.size())
                lines.emplace_back(move(tokens));
        }

I'm confused about how that last if statement works, other than the fact that it takes each row and adds it to the vector lines unless it doesn't contain anything, and maybe that's all there is to it, but maybe not. I'm also still wondering how other posters manage to get code or output windows side by side in their posts.
Last edited on
1
2
3
4
5
6
7
8
9
10
while(getline(Test,line))
        {
            vector<string> tokens;
            stringstream ls(line);
            string token;
            while (ls>>token)
                tokens.push_back(token);

            lines.emplace_back(tokens.size() ? move(tokens) : std::vector<string>(1)) ;
        }

would be one way to do it.

Another way:
1
2
3
4
5
6
7
8
9
10
11
12
13
while(getline(Test,line))
        {
            vector<string> tokens;
            stringstream ls(line);
            string token;
            while (ls>>token)
                tokens.push_back(token);

            if (tokens.size() == 0)
                tokens.resize(1) ;   // resize the vector - there will be one default constructed (empty) string in the vector.

            lines.emplace_back(move(tokens)) ;
        }


I'm also still wondering how other posters manage to get code or output windows side by side in their posts.

Within a code block, place the string "---" (without the quotes) at the beginning of a line. Everything following that will be in a side-by-side output window.

[code]This is the first line in the code window.
This is the second line in the code window.
---
This is in the output window.
[/code]

looks like this:
1
2
This is the first line in the code window.
This is the second line in the code window.
This is in the output window.

Last edited on
Thanks, cire. This should simplify some posts in the future.
1
2
Testing
123
Output
Last edited on
Topic archived. No new replies allowed.