Vectors and File I/O

I was using this forum as a guide and it raised a couple questions that I am having a little bit of a hard time finding clear answers to. http://www.cplusplus.com/forum/beginner/82004/ And I found answers but ended up getting even more confused on another forum. So, since everyone is very helpful here and I can understand what's being said, I have a quick question:

I just recieved a criticism form my teacher that I needed to simplify my arrays in a project that I was working on and the best way to do this is by making a multidimensional vector and another parallel with it(kinda).

My MDV is going to look something like this:

Cookies || Boxes Sold || # of Entries || Avg per Entry
name1 || # || # || #
name2 || # || # || #
name3 || # || # || #
name4 || # || # || #
name5 || # || # || #
name6 || # || # || #
name7 || # || # || #
name8 || # || # || #

I'm using this vector parallel with a dynamic vector with names of each student. My biggest concern is that after the data is output to my txt file, how do I bring it back into the vector to display the numbers if it reads horizontally like the example with spaces or tabs in between? As of right now, I don't have any code. I'm just playing around with it to figure out what I need to do in a blank program.

Also, how would I call on the whole example when I'll have a minimum of five separate examples in one .txt file. I don't quite understand how to initialize start and stop points for inputting/outputting to control the process if need be. (Might not even need to, just curious and figured that it'd speed up the program if I controlled it. . .)

Thanks in advance!
Last edited on
Well, one doesn't necessarily need to marry the file input and output with the data structure you use in the program. Of course, not doing so introduces a level of indirection, but it's a very thin one that's pretty easy to deal with.

Consider a utility class called FileEntry which is just responsible for reading and writing some information to a file.

FileEntry.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef FILEENTRY_H___
#define FILEENTRY_H___

#include <string>

struct FileEntry
{
  std::string name ;
  unsigned sold ;
  unsigned nEntries ;
};

std::ifstream& operator>>(std::ifstream& is, FileEntry& entry) ;
std::ofstream& operator<<(std::ofstream& os, const FileEntry& entry) ;

#endif 


FileEntry.cpp:
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
#include <fstream>
#include <string>
#include <sstream>
#include "FileEntry.h"

const char field_delimiter = '\t' ;

std::ifstream& operator>>(std::ifstream& is, FileEntry& entry)
{
    std::string input ;

    if ( std::getline(is, input) )  
    {
        std::istringstream in(input) ;

        // we should probably check to make sure the following operations are
        // successful, but for brevity's sake...

        std::getline(in, entry.name, field_delimiter) ;
        in >> entry.sold >> entry.nEntries ;
    }

    return is ;
}

std::ofstream& operator<<(std::ofstream& os, const FileEntry& entry )
{
    // output is easy!
    os << entry.name << '\t' << entry.sold << '\t' << entry.nEntries << '\n' ;
    return os ;
}


A couple things bear explaining. I don't store the "Average per entry" because it can be easily calculated from other data we're storing. I don't know whether name should consist of a string with a space in it (for example a first and last name,) so to get around that I assume that the file is tab delimited (ie. there's a '\t' character between each data item on a given line.) operator<< and operator >> are just responsible for writing and reading one line of data to a file. Once that's known, I think the code is easy enough to understand.

You'll notice that there is no notion of containers in this code. So, how do we use it?

Well, let's a assume we have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Whatever
{
public:
    // ...
    bool writeToFile( const std::string& fname ) const ;
    bool readFromFile( const std::string& fname ) ;
    // ...
private:

    struct OrdersInfo
    {
        unsigned sold, entries ;

        OrdersInfo(unsigned s, unsigned e) : sold(s), entries(e) {}
    };

    std::vector<std::string> names ;
    std::vector<OrdersInfo> info ;

};



Whatever is responsible for managing a parallel set of related vectors with a one-to-one relationship. It is obviously an incomplete type, but it should serve for illustration purposes. You'll notice there is no mention of a FileEntry in the class definition. The only place any mention of a FileEntry need show up is in the definition of the member functions that read and write from a file, in this case Whatever::readFromFile and Whatever::writeToFile. In that sense a FileEntry is just an implementation detail of Whatever.

Let's take a look at possible implementations for read and write:

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
bool Whatever::writeToFile(const std::string & fname) const
{
    std::ofstream out(fname, std::ios::out | std::ios::trunc) ;

    for ( unsigned i=0; i<names.size(); ++i )
    {
        // create a FileEntry object with our data and write it to the file.
        const FileEntry entry = { names[i], info[i].sold, info[i].entries } ;
        out << entry ;
    }

    return out ;
}

bool Whatever::readFromFile(const std::string & fname)
{
    std::ifstream in(fname) ;
    if ( in.is_open() )
    {
        // If we're reading from file, we want to get rid of
        // the info that's already in memory.
        names.clear() ;
        info.clear() ;
        FileEntry entry ;

        // read a FileEntry object from the file and convert it to our format.
        while ( in >> entry )
        {
            names.push_back(entry.name) ;
            info.push_back(OrdersInfo(entry.sold, entry.nEntries)) ;
        }
        return true ;
    }
 
    return false ;
}


I think the code and comments are fairly straightforward, but if you've got any questions feel free to ask. None of this code has been tested, by the way, so: user beware!

Depending on what you're doing, std::map may be a better choice for the internals of "Whatever"
Wow! Thanks! I'll look at it and play around with it today in 3 hrs to make sure I understand it before I ask any brain fart questions.

Thanks again!
Ok so all of this is to check to make sure that I'm on the right page, so please bear with me. I think as of right now I'm a little in over my head.

For the header file, I changed some names for my specific program, but left some names as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef FILEENTRY_H___
#define FILEENTRY_H___

#include <string>
#include <vector>

using namespace std;

struct FileEntry
{
  vector<string> info;
  unsigned sold;
  unsigned nEntries;
};

ifstream& operator>>(ifstream& is, FileEntry& entry) ;
ofstream& operator<<(ofstream& os, const FileEntry& entry) ;

#endif 


I changed this part: vector<string> info; in hopes of it not only working with the rest of the code but to make it dynamic later too. I hope that it'll still work.

For the .cpp file, this is what I have thus far. I'm more worried that it's in the right order, then I'm wondering if I still need to build the vectors in int main() or not.

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
// Cookie Counter

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include "FileEntry.h"

using namespace std;

const char field_delimiter = '\t';

class Cookies
{
public:
    // ...
    bool writeToFile( const string& fname ) const ;
    bool readFromFile( const string& fname ) ;
    // ...
private:

    struct OrdersInfo
    {
        unsigned sold, entries ;

        OrdersInfo(unsigned s, unsigned e) : sold(s), entries(e) {}
    };

    vector<string>names;
    vector<OrdersInfo>info;

};

bool Cookies::writeToFile(const string & fname) const
{
    ofstream out(fname, ios::out | ios::trunc) ;

    for ( unsigned i=0; i<names.size(); ++i )
    {
        // create a FileEntry object with our data and write it to the file.
        const FileEntry entry = { names[i], info[i].sold, info[i].entries } ;
        out << entry ;

    }

    return out ;
}

bool Cookies::readFromFile(const string & fname)
{
    ifstream in(fname) ;
    if ( in.is_open() )
    {
        // If we're reading from file, we want to get rid of
        // the info that's already in memory.
        names.clear() ;
        info.clear() ;
        FileEntry entry;

        // read a FileEntry object from the file and convert it to our format.
        while ( in >> entry )
        {
            names.push_back(entry.info) ;
            info.push_back(OrdersInfo(entry.sold, entry.nEntries)) ;
        }
        return true ;
    }
 
    return false ;
}

ifstream& operator>>(ifstream& is, FileEntry& entry)
{
    string input;

    if (getline(is, input))  
    {
        istringstream in(input);

        // we should probably check to make sure the following operations are
        // successful, but for brevity's sake...

        getline(in, entry.info, field_delimiter);
        in >> entry.sold >> entry.nEntries;
    }

    return is;
}

ofstream& operator<<(ofstream& os, const FileEntry& entry )
{
    // output is easy!
    os << entry.info << '\t' << entry.sold << '\t' << entry.nEntries << '\n';
    return os;
}

int main()
{
	vector<string> names;
	names.push_back("Names");
	names.push_back("Alex");
	names.push_back("Charles");
	names.push_back("Dustin");
	names.push_back("Lori");
	names.push_back("Mark");

	vector<string>::iterator myIterator;
	vector<string>::const_iterator iter;

	const int rows = 41;
	const int columns = 3;

	vector<int> info;
	info.resize(rows * columns);

	for (int row = 0; row < rows; row++)
	{
	    for (int col = 0; col < columns; col++)
		{
	        info[row * columns + col] = row * col;
	    }
	}


So this is what I have thus far. Like I said, I am hoping that I'm on the right path and am wondering if I should declare the vectors somewhere else or not, if I even need to build them. I also have my cookie sales data in cookieCounts.txt if that helps at all.

Sorry for the headache, I feel like the answer is about to bite me but I don't see it. Thanks in advance!
Topic archived. No new replies allowed.