Read in file as string and store into struct variables

Hello, I am in a basic programming class, and we have a lab that I am pretty stuck on. The instructions are:
"The object of this program is to rearrange the data in two input files into two output file that have the same names. The input version of the files will contain a mixture of male and female names, the program rearranges the contents of the two files so that all of the female names are in one of the data files and all of the male names are in the other file. You should define a struct for the data and read in the data from both filesinto dynamically allocated arrays of the struct.

The program will also report the total number of female names and males names found in each of the input files to the display before the program ends. Your output to the display should look like the following example:

Example Output

datafile1.txt contained x female names and y male names
datafile2.txt contained y female names and y males name
where x and y are of course the actual counts of the names in the files.

The difficulty here is that yoou will be using the same file for input and output (but not at the same time). To use the same files You will need to first open the files for input and close then after reading in all of the data. When finished reading in the data close the input file stream. Then you will need to open the same files for output. Since we need to look at all of the input data in order to count the totakl number of each genfer we need to read both input files into memory so we can then close the files and reopen them as output files. Each of the input files contain the total number of entries in the file in the first line of the file. Each line of data will contain a name consisting of a first name and a last name seperated by a space than after another space there will be a gender indicator of either "F" or "M".

EXAMPLE INPUT datafile1.txt

4
Dana Smith F
River Jones M
Jordan Pollic F
Liska Fanta M
EXAMPLE INPUT datafile2.txt

5
Ray Harper F
Morgan McMillan F
Jessie James M
Reese Moore M
Taylor Kale F

The number of data entries that is contained in the first line of each file will give you the information you need to allocate enough dynamic memory to read the entire file into memory. You will output all of the female names into the file datafile1.txt and all of the male names into the file datafile2.txt. The first line of each output file will need to contain the total number of names followed by a space and then a text string indicating the gender of the names in the that output file.

EXAMPLE OUTPUT datafile1.txt

5 female
Dana Smith
Jordan Polick
Ray Harper
Morgan McMillan
Taylor Kale
EXAMPLE OUTPUT datafile2.txt

4 male
River Jones
Liska Fanta
Jessie James
Reese Moore"


My main problem is reading in the file and sorting it into the struct variables. I know what I have on line 43 is wrong, but it gets across what I want to do I think. My other problem is actually sort the two files, I know how to output into stream files, but I don't know where to start with this.

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

struct dataFileInfo {
    std::string firstName;
    std::string lastName;
    char gender;
    };
    
bool readRecord(std::ifstream &, dataFileInfo &);
void (std::ostream &, );

int main()  {
    dataFileInfo record;
    
    std::ifstream myFile;
    myFile.open("datafile1.txt");
    if(!myFile)   {
        std::cout << "Sorry, bad file.";
        exit(0);
    }
    readRecord(myFile, record);
    myFile.close("datafile1.txt");
    
    
    myFile.open("datafile2.txt");
    if(!myFile) {
        std::cout << "Sorry, bad file.";
        exit(0);
        }
    readRecord(myFile, record);
    myFile.close("datafile2.txt");
    
    std::ostream myFile;
    myFile.open("datafile1.txt");
    
    
}

bool readRecord(std::ifstream &myFile, dataFileInfo &record)  {
    std::string incomingData;
    record.firstName = getline(myFile, incomingData, '\0');
   
    // HELP ME PLEASE
    
    
    }
    
void (std::ostream &myFile, )    {
    
    }
Last edited on
do you know vector?
I think you need a vector of dataFileInfo
so that you can loop over the files, reading each line into one dataFileInfo.
then you can iterate over the data twice, once to pick off M records, once to pick off F records, and write them to the file. Does this make sense?

I personally would change read record to read file,
and do it like this (roughly)

while (myfile >> tmprecord.firstname >> tmprecord.lastname >> tmprecord.gender)
{
vec.push_back(tmprecord);
}

and then
for(… vec.size())
if(vec[x] == 'M')
write to file
file close, reopen, etc

for(… vec.size())
if(vec[x] == 'F')
write to file

if you can't use vector, you can use what you do know, a bigger than needed array, a pointer, etc. You can make yet another routine to count the lines in the file, doing nothing else, and then allocate a pointer, if you want to be precise.
Last edited on
It does make sense, but we don't really know vectors yet, so I don't think we can use them.

She just taught us about pointers so I'm not complete sure how they work, but I know I can use them here.
Last edited on
ok. then use a pointer or array to the same effect.
see if you can do what I edited into it, to count the lines and make a pointer "array" of your objects..

roughly again
int count_lines(stuff..)
{
int count = 0;
while(file.getline(bogus))
count++;
return count;
}

//openfile, closefile or do that in count_lines, as you see fit. I think it goes in the function.
dataFileInfo * dfp = new dataFileInfo [count_lines(stuff)];

Last edited on
Would I be able to do this to get the number of names? I plan on getting the amount of males and females while I sort them in the if statements later on. So for reading into the structure variables could I do this since the first character of the string is the number of names?

1
2
3
4
5
6
7
8
bool readFile(std::ifstream &myFile, dataFileInfo &record)  {
    std::string incomingData;
    int numOfNames = incomingData.at(0);
    for(int i = 0, i < numOfNames, i++) {
        // Sort into structure variables
        }
    
    }
Last edited on
yes and no. I see now, sorry... yes, the first row is how many, but just read that directly into an integer.

int num;
file >> num; //do it this way.


the ascii value of 4 is NOT 4. It is like 40 or something. You can't use it that way. you can convert the letter (one letter only using your logic) to a number but if it had 10+ rows, it wouldn't work, you would see the '1' only using this approach. The above code converts it for you.

forget what I said about looping to count rows, I didn't see that it was in the file data.
Last edited on
So even though the input files first line is single number and it says how many names are in the file, I still have to iterate with getline?
no. see above, I changed it.
its probably worth your time to see the difference.

do it with .at and write out the integer value you get.
then look at an online ascii table at the 'number' as text for '4'
then do it my way :)
Last edited on
I worked on the code a bit and this is what I have now:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool readFile(std::ifstream &myFile, dataFileInfo &record)  {
    int numOfNames;
    myFile >> numOfNames;
    std::string incomingData;
    
    dataFileInfo information[numOfNames];
    for(int i = 0; i < numOfNames; i++) {
        getline(myFile, incomingData);
        std::istringstream iss(incomingData);
        struct dataFileInfo entry;
        iss >> entry.firstName;
        iss >> entry.lastName;
        iss >> entry.gender;
        information[i] = entry;
        }
    }
that looks closer. The getline/stream thing is a little odd to me; I would just directly read each field from the file, but it works and yours may be better for error checking in the future (you didn't do any yet). You could just say myfile>> field and remove the stream code if you wanted to. /shrug its fine as is, just offering an option. You do not need keyword struct in c++. Thats a C thing. It works, but its redundant; making a struct defines it as a type already. You can also directly load your array: myfile>> array[i].gender etc.... also not critical but you copy the data to a temp then copy it to the array, which is pointless copying. Your array should probably be passed into the function or returned from it (I did this as you don't seem to use the bool return). It is created, loaded, and discarded as you wrote it. You may NOT use variables to declare arrays in standard c++, this is allowed by some compilers but is illegal, so avoid it.

you can "cheat", a little by the way, and count the genders as you read.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
dataFileInfo * readFile(std::ifstream &myFile, dataFileInfo &record, int &Fcount, int &Mcount)  
{
    int numOfNames;
    myFile >> numOfNames;
    std::string incomingData;
    Mcount=Fcount=0;
    dataFileInfo *information = new dataFileInfo [numOfNames];
    for(int i = 0; i < numOfNames; i++) 
{
        getline(myFile, incomingData);
        std::istringstream iss(incomingData);
        [s]struct dataFileInfo entry;
        myFile>>         information[i].firstName;
        myFile>>         information[i].lastName;
        myFile>>         information[i].gender;
         if(        information[i].gender == 'M') Mcount++;
         else Fcount++; //assumes M or F and nothing else/valid file format 
//add here anything else you need to know about gender to solve problem?
        information[i] = entry;
        }
return information; //delete this in main when done using it.
    }


note that if the file is poorly formed, eg human created, this code may error out. If its machine generated and assured to be correct, it will be fine. Input handling is a bigger topic; get it working first.
Last edited on
Use two vectors, one for names of each gender. For 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
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
#include <iostream>
#include <string>
#include <vector>
#include <fstream>

// read names from the file into the appropriate vectors
void read_names( const std::string& file_name,
                 std::vector<std::string>& female_names,
                 std::vector<std::string>& male_names )
{
    if( std::ifstream file{file_name} )
    {
        long long cnt ;
        file >> cnt ;
        for( long long i = 0 ; i < cnt ; ++i )
        {
            std::string first_name ;
            std::string last_name ;
            char gender ;
            file >> first_name >> last_name >> gender ;

            if( gender == 'F' || gender == 'f' )
                female_names.push_back( first_name + ' ' + last_name ) ;
            else if( gender == 'M' || gender == 'm' )
                male_names.push_back( first_name + ' ' + last_name ) ;
        }
    }
}

int main()
{
    const std::string file_1_name = "datafile1.txt" ;
    const std::string file_2_name = "datafile2.txt" ;

    std::vector<std::string> female_names ;
    std::vector<std::string> male_names ;

    // read into the empty vectors from file 1
    read_names( file_1_name, female_names, male_names ) ;

    // append to the vectors from file 2
    read_names( file_2_name, female_names, male_names ) ;\

    // write female_names into file 1 (overwrite the file)
    if( std::ofstream file{file_1_name} )
    {
        file << female_names.size() << " female\n" ;
        for( const std::string& n : female_names ) file << n << '\n' ;
    }

    // write male_names into file 2 (overwrite the file)
    if( std::ofstream file{file_2_name} )
    {
        file << male_names.size() << " male\n" ;
        for( const std::string& n : male_names ) file << n << '\n' ;
    }
}

... read in the data from both files into dynamically allocated arrays of the struct.

Has anybody bothered to read the task??
I hope this helps

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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
 * @author    blongho.
 *
 * @brief	    Given two files, read the contents in the file,
 * 		    separate the males from the females and save the females 
 * 		    in a different file and males in another file.
 * 		    At the end of the program, tell user what the files contained.
 * 			
 * 		  Constraints: Use dynamic arrays
 * 		  Origin: http://www.cplusplus.com/forum/beginner/242250/#msg1075756
 * 			
 * @since	2018-09-10
 */

#include <iostream>
#include <fstream>
#include <string>

/** datafile structure */
struct DataFile
{
	int numOfRecords;
	std::string firstName;
	std::string lastName;
	char sex;

	void setNumOfRecords(std::ifstream &ifs, const std::string &filename) {
		ifs.open(filename, std::ios::in); // open the 
		if (ifs) {
			ifs >> numOfRecords;
		}
		else {
			std::cout << "Could not open file {" << filename << "}" << std::endl;
		}
	}
};

/**
 * After you have the number of records, read the rest values and put into a
 * DataFile object
 *
 * @param [in,out]	ifs		The ifstream object.
 * @param [in,out]	datafile	[in,out] array of DataFile objects.
 * @param 		records 	The number of records in the file.
 * @param [in,out]	males   	The males the number of males in the file(to be obtained).
 * @param [in,out]	females 	The females the number of females int he file (to be obtained).
 */

void readRecords(std::ifstream &ifs, DataFile * &datafile, int records, int &males, int &females) {
	int ind{};
	while (ind < records) {
		DataFile tmp;
		ifs >> tmp.firstName >> tmp.lastName >> tmp.sex;
		datafile[ind++] = tmp;

		if (tmp.sex == 'F' || tmp.sex == 'f') {
			females++;
		}
		else {
			males++;
		}
	}
}

/**
 * Process an array of DataFile objects and save males and females into
 * different files
 *
 * @param [in,out]	female  	The file to save the female records
 * @param [in,out]	male		The file to save the male records
 * @param [in,out]	datafile	[in,out] The dataFile array .
 * @param 		records 	The records the number of records in the DataFile array.
 */
void saveRecord(std::ofstream &female, std::ofstream&male, DataFile * &datafile, int records) {
	for (int i = 0; i < records; i++) {
		if (datafile[i].sex == 'F' || datafile[i].sex == 'f') {
			female << datafile[i].firstName << ' ' << datafile[i].lastName << '\n';
		}
		else {
			male << datafile[i].firstName << ' ' << datafile[i].lastName << '\n';
		}
	}
}

// Say what a file contains
void decribeContent(const std::string & filename, int males, int females, std::ostream &os = std::cout) {
	os << filename << " contained " << females << " female names and "
		<< males << " male names\n";
}

int main()
{
	DataFile dataFile1, dataFile2; 
	std::ifstream ifs, ifs2;
	std::string infile1{ "datafile1.txt" };
	std::string infile2{ "datafile2.txt" };
	std::string outfile1{ "males.txt" };
	std::string outfile2{ "females.txt" };

	dataFile1.setNumOfRecords(ifs, infile1);
	dataFile2.setNumOfRecords(ifs2, infile2);

	int numOfRecordsInDataFile1 = dataFile1.numOfRecords;
	int numOfRecordsInDataFile2 = dataFile2.numOfRecords;

	DataFile * records1 = new DataFile[numOfRecordsInDataFile1];
	DataFile * records2 = new DataFile[numOfRecordsInDataFile2];

	std::ofstream maleFile{ outfile1 };
	std::ofstream femaleFile{ outfile2 };

	int males1{}, females1{}, // males and females from datafile1
		males2{}, females2{}; // males and females from datafile2
	if (ifs) {
		readRecords(ifs, records1, numOfRecordsInDataFile1, males1, females1);
	}
	else { std::cout << " Could not open the file '" << infile1 << "'" << std::endl; }

	if (ifs2) {
		readRecords(ifs2, records2, numOfRecordsInDataFile2, males2, females2);
	}
	else {
		std::cout << " Could not open the file '" << infile2 << "'" << std::endl;
	}

	// Start saving files
	femaleFile << females1 + females2 << " Females\n";
	maleFile << males1 + males2 << " Males\n";

	// extract data from first record and save to file
	saveRecord(femaleFile, maleFile, records1, numOfRecordsInDataFile1);
	saveRecord(femaleFile, maleFile, records2, numOfRecordsInDataFile2);
	

	// describe records
	decribeContent(infile1, males1, females1);
	decribeContent(infile2, males2, females2);

	// free resources 
	ifs.close();
	ifs2.close();
	maleFile.close();
	femaleFile.close();
	delete[]records1;
	delete[]records2;
	return 0;
}
Last edited on
yes, I have read his assignment.
I recommended adding the counters so he could write the new files with slightly less work, assuming the new files need the 'number of items' in the first line again like the originals --- thinking he could call the revised function twice, once for each input file, and total up the #M and #F, then open the file variable again to write M, using that total, and again for F, using that total. Without the counter embedded in the read, he would need to iterate once to count and once to write, /shrug ... it violates the idea of a function doing 1 thing but it saves pointless data crawling. I err on the side of speed usually.

He said no vectors, so I showed allocating a dynamic array of the thing and returning that as well in my changes.

I am not sure what I did so very wrong in the revision that everyone is offering all these alternatives --- I thought the last version of the revised code was pretty close to what he needed.
Last edited on
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
#include <iostream>
#include <fstream>
#include <string>
using namespace std;


struct Person
{
   string firstName;
   string lastName;
   char gender;
};


istream &operator >> ( istream &strm, Person &p ) { return strm >> p.firstName >> p.lastName >> p.gender; }

ostream &operator << ( ostream &strm, const Person &p ) { return strm << p.firstName << " " << p.lastName; }


//==========================================================


int main()
{
   string filenames[] = { "datafile1.txt", "datafile2.txt" };

   // Get the total size of dynamically-allocated arrays (sighs! - much easier with vectors)
   int N = 0, n;
   for ( string s : filenames )
   {
      ifstream in( s );
      in >> n;
      N += n;
      in.close();
   }
   Person *people = new Person[N];

   // Then read the files properly
   int male = 0, female = 0;
   N = 0;
   for ( string s : filenames )
   {
      int male0 = male, female0 = female;
      ifstream in( s );
      in >> n;
      for ( int i = N; i < N + n; i++ )
      {
         in >> people[i];
         if ( people[i].gender == 'F' ) female++;
         else                           male++;
      }
      N += n;
      cout << s << " contains " << female - female0 << " female names and " << male - male0   << " male names" << '\n';
      in.close();
   }

   // Now dump them in the first two files
   ofstream out0( filenames[0] );   out0 << female << '\n';
   ofstream out1( filenames[1] );   out1 << male   << '\n';
   for ( int i = 0; i < N; i++ )
   {
      if ( people[i].gender == 'F' ) out0 << people[i] << '\n';
      else                           out1 << people[i] << '\n';
   }
   out0.close();
   out1.close();

   // And tidy up
   delete [] people;       // always wanted to write that!
}

Last edited on
@lastchance,
I don't see you using 2 arrays as requiered.
Thomas1965 wrote:
@lastchance,
I don't see you using 2 arrays as requiered.

Required where? I see nowhere in the specification that I need two arrays.

If I were using vectors, which the discussion suggests are not to be used, then I might (but still probably wouldn't) use separate vectors for male and female. Otherwise, the only reasonable way of having two dynamically-allocated arrays is one for each input file (since the sizes would be known after the first line is read). That seems like overkill.

Depends if you regard a vector as a dynamically-allocated array or not. Seemingly the OP doesn't want to use them, but they would make the task substantially easier here.
Last edited on
Required where? I see nowhere in the specification that I need two arrays.
Since they use plural it meens more than one
read in the data from both files into dynamically allocated arrays

Yes using two vectors would be easiest, but I guess these tasks were for C a long time ago and never adopted to modern C++.
This any better? Two arrays, one for each file.

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


struct Person
{
   string firstName;
   string lastName;
   char gender;
};

//------------------

istream &operator >> ( istream &strm, Person &p ) { return strm >> p.firstName >> p.lastName >> p.gender; }

ostream &operator << ( ostream &strm, const Person &p ) { return strm << p.firstName << " " << p.lastName; }

//------------------

Person *readFile( string filename, int &n, int &male, int &female )
{
   male = female = 0;
   ifstream in( filename );
   in >> n;
   Person *people = new Person[n];
   for ( int i = 0; i < n; i++ )
   {
      in >> people[i];
      if ( people[i].gender == 'F' ) female++;
      else                           male++;
   }
   in.close();
   cout << filename << " contained " << female << " female names and " << male << " male names\n";
   return people;
}

//------------------

void writeFile( ostream &strm, Person *people, int n, char gender )
{
   for ( int i = 0; i < n; i++ )
   {
      if ( people[i].gender == gender ) strm << people[i] << '\n';
   }
}


//==========================================================


int main()
{
   int n1, n2, m1, m2, f1, f2;

   Person *people1 = readFile( "datafile1.txt", n1, m1, f1 );
   Person *people2 = readFile( "datafile2.txt", n2, m2, f2 );

   // Write females
   ofstream outf( "datafile1.txt" );
   outf << f1 + f2 << " female names\n";
   writeFile( outf, people1, n1, 'F' );
   writeFile( outf, people2, n2, 'F' );
   outf.close();

   // Write males
   ofstream outm( "datafile2.txt" );
   outm << m1 + m2 << " male names\n";
   writeFile( outm, people1, n1, 'M' );
   writeFile( outm, people2, n2, 'M' );
   outm.close();

   // And tidy up
   delete [] people1;  
   delete [] people2;
}

Last edited on
@lastchance,
looks perfect now -> 100%
Topic archived. No new replies allowed.