Edit a file

I have a .txt file like this

1
2
3
4
5
6
7
8
9
10
11
No  Student ID   Last name     First name  Gender    DoB
1 18127221 Nguyen Van Tuan Male 4/11/2000
2 18127222 Tran Van A Female 9/6/2000
3 18127223 Le Nguyen Ngoc Female 8/1/2000
4 18127224 Donald Trump Male 12/5/2000
5 18127225 Mike Pence Male 21/4/2000
6 18127226 Nguyen Xuan Phuc Male 2/9/2000
7 18127227 Nguyen Phu  Trong Male 12/12/2000
8 18127228 Pham Binh Minh Male 13/3/2000
9 18127229 Vu Duc Dam Male 3/2/2000
10 18127230 To  Lam Male 4/6/2000

I'm trying to write code that can edit an element of this file. For example, I want to change the ID 18127221 into another number, such as 18127777. Here what I've tried so far:
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
	string stuid, ID, last_name, first_name, gender, dob;
	cout << "Enter the student's ID you want to change: ";
	cin >> stuid;
	ofstream out;
	out.open("E:/student.txt", std::ofstream::trunc);
	if (!out)
	{
		cout << "Error!\n";
	}
	else
	{
		for (int i = 0; i < index; i++)
		{
			if (student[i].id == stuid)
			{
				cout << "1. Change ID\n" << "2. Change last mame\n" << "3. Change first name\n" << "4. Change gender\n" << "5. Change DoB\n";
				int e;
				cin >> e;
				switch (e)
				{
				case 1:
					cout << "New ID: ";
					cin >> ID;
					student[i].id = ID;
					out << ID;
				}
			}
		}
	}

But when I run it and open the file, there was s nothing but the new elements I entered. Why did this happened and how to fix it?
Make a struct for the record information:

1
2
3
4
5
6
7
struct Student
{
  unsigned long ID;
  std::string name;
  Gender gender; // A simple enum?
  Date DOB;  // This can be whatever you want. For simplicity, make it a std::string.
};

Write functions to load and save said structure from a line of text:

1
2
3
4
5
6
7
8
9
std::istream& operator >> ( std::istream& ins, Student& student )
{
  std::string s;
  getline( ins, s );
  ...
  // (Your file format makes this a little tricky. Hint: search for the words
  //  "Male" and "Female" in the string. Then you have both found the gender
  //  AND you have found the end of the name.)
}
1
2
3
4
5
6
7
8
std::ostream& operator << ( std::ostream& outs, const Student& student )
{
  return outs 
    << student.ID << " " 
    << student.name << " "
    << student.gender << " "
    << student.DOB << "\n";
}


Now you need functions to read and write the entire file.

1
2
3
4
5
6
7
8
9
std::istream& operator >> ( std::istream& ins, std::vector <Student> & students )
{
  int No;
  students.clear();
  Student student;
  while (ins >> No >> student)
    students.push_back( student );
  return ins;
}
1
2
3
4
5
6
7
std::ostream& operator << ( std::ostream& outs, const std::vector <Student> & students )
{
  int No = 1;
  for (const Student& student : students)
    outs << No++ << " " << student;
  return outs;
}



Now for the trick. Read the entire file. Make changes. Write the entire file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
  std::vector <Student> students;

  // Load the file
  {
    std::istream f( "students.txt" );
    f >> students;
  }

  // Ask for the change desired
  unsigned long ID;
  std::cout << "Enter the student ID of the student you want to change: ";
  std::cin >> ID;
  std::cin.ignore( std::numeric_limits <std::streamsize> ::max(), '\n' );

  ... and so on ...

  // Update the student file
  {
    std::ofstream f( "students.txt" );
    f << students;
  }


Also, hint:
  • Use functions to do stuff, like make a function to ask for a new ID, check that it isn’t
     already in use, and assign the new ID to the student.

Hope this helps.
1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <fstream>
#include <limits>
#include <string>
#include <iomanip>
using namespace std;
struct Student
{
	string no, id, last_name, first_name, gender, dob;
};


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
	size_t index{}, tIndex{};
	ifstream in;
	in.open("E:/students.csv");
	Student student[100];
	string  No, student_id, Last_name, First_name, Gender, DoB;
	if (!in.is_open())
	{
		cout << "Error!\n";
	}
	else
	{
		getline(in, No, ',');
		getline(in, student_id, ',');
		getline(in, Last_name, ',');
		getline(in, First_name, ',');
		getline(in, Gender, ',');
		getline(in, DoB, '\n');
		while (getline(in, student[index].no, ','))
		{
			getline(in, student[index].id, ',');
			getline(in, student[index].last_name, ',');
			getline(in, student[index].first_name, ',');
			getline(in, student[index].gender, ',');
			getline(in, student[index++].dob, '\n');
		}
		in.close();
	}

Those are the thing exiting in my program.
I see you use a vector for this, how about the array (since my project is required using only array). Do I have to do a bunch of code to edit the whole list rather than push_back()?
The second part of code is from main()
Last edited on
Arrays and vectors really aren’t very different.

1
2
3
4
// instead of:

std::vector <Student> students;
// use:
constexpr int MAX_STUDENTS = 100;
Student students[ MAX_STUDENTS ];
int num_students = 0;


1
2
// instead of:
students.push_back( student );
// use:
students[ num_students++ ] = student;



The trick is that you cannot directly edit stuff in a file (unless that file is a fixed-length record, which yours is not).

So you need to read the entire file into an array (or vector, or any kind of list).
Then make the modifications to your array.
Then write the array back to file.


I admit I am not familiar with Vietnamese naming conventions. Is Nguyen usable as a given name?

In any case, the list of student names seem to totally ignore the header line, and have two or three words each. Complicating that is that the Vietnamese names seem to (mostly?) listed in traditional family-middle-first name order, while Western names are listed in the traditional first-family order.

This is really hard to parse into something like first/last name. Typically, software systems will list family name(s) first, followed by a comma, then given name(s) in first-middle order:

    Pence, Mike
    Tran, Van A      
(I’m presuming Van is her first name) 

This removes ambiguities (the important ones, anyway) and makes parsing much simpler. For example, I can unambiguously list:

    Hernandez Herrerra de Seville, Nayeli Rosa

From that I can get her primary surname (Hernandez) but also the complete one, and also her first given name (Nayeli).

For your input file, all I can say is that I know where the name begins (after the Student ID) and where it ends (before the word "Male" or "Female").

So, yeah, names are much more complicated than people realize...

So if you want to keep it simple, just stick to ONE family name and ONE given name in the student list:
No  Student ID   Last name     First name  Gender    DoB
1 18127221 Nguyen Tuan Male 4/11/2000
2 18127222 Tran Van Female 9/6/2000
3 18127223 Le Ngoc Female 8/1/2000
4 18127224 Trump Donald Male 12/5/2000
5 18127225 Pence Mike Male 21/4/2000
6 18127226 Nguyen Phuc Male 2/9/2000
7 18127227 Nguyen Trong Male 12/12/2000
8 18127228 Pham Minh Male 13/3/2000
9 18127229 Vu Dam Male 3/2/2000
10 18127230 To Lam Male 4/6/2000

If you can do that, it makes parsing into family name and given name much easier.


Remember that std::getline() reads an entire line. But you have multiple things on a line. So...

Use getline() to read the entire record, then repack it into an istringstream, and then use the >> operator to get the pieces:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Student read_student( std::istream& in )
{
  // First, get the WHOLE line
  std::string s;
  getline( in, s );

  // Next, repack it into a stringstream
  std::istringstream ss( s );
  
  // Now get the parts
  Student student;
  ss
    >> student.no
    >> student.id
    >> student.last_name
    >> student.first_name
    >> student.gender
    >> student.dob;

  return student;
}

If you cannot fix the input file format, though, you will have to do extra work:

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
Student read_student( std::istream& in )
{
  // First, get the WHOLE line
  std::string s;
  getline( in, s );

  // Next, repack it into a stringstream
  std::istringstream ss( s );

  // Now get the parts
  Student student;
  // (first the listing number and the Student ID)
  ss
    >> student.no
    >> student.id;

  // (the Name ends when we find the Gender)
  ss >> student.name;
  while (ss)
  {
    ss >> s;
    if ((s == "Male") || (s == "Female")) break;
    student.name += " " + s;
  }
  student.gender = s;

  // (after Gender is just DOB)
  ss >> dob;

  return student;
}

Fortunately, writing a Student to file is really, really easy.


Hope this helps.
Can I put both reading the file and editing it into a function?
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
void EditStu(Student student[], int index)
{
	string stuid, ID, Last_name, First_name, Gender, Dob;
	cout << "Enter the student's ID you want to change: ";
	cin >> stuid;
	ofstream out;
	out.open("E:/students.csv", std::ofstream::trunc);
	if (!out)
	{
		cout << "Error!\n";
	}
	else
	{
		for (int i = 0; i < index; i++)
		{
			if (student[i].id == stuid)
			{
				cout << "1. Change ID\n" << "2. Change last name\n" << "3. Change first name\n" << "4. Change gender\n" << "5. Change DoB\n";
				int e;
				cin >> e;
				switch (e)
				{
				case 1:
					cout << "New ID: ";
					cin >> ID;
					student[i].id = ID;
					break;
				case 2:
					cout << "New last name: ";
					cin >> Last_name;
					student[i].last_name = Last_name;
					break;
				case 3:
					cout << "New first name: ";
					cin >> First_name;
					student[i].first_name = First_name;
					break;
				case 4:
					cout << "New gender: ";
					cin >> Gender;
					student[i].gender = Gender;
					break;
				case 5:
					cout << "New dob: ";
					cin >> Dob;
					student[i].dob = Dob;
					break;
				}

			}
		}
		out << "No,Student ID,Lastname,Firstname,Gender,DoB\n";
		for (int i = 0; i < index; i++)
		{
			out << student[i].no << ",";
			out << student[i].id << ",";
			out << student[i].last_name << ",";
			out << student[i].first_name << ",";
			out << student[i].gender << ",";
			out << student[i].dob << endl;
		}
		out.close();
	}
}

index is the number of rows in the file, staring from the first row 1 18127221 Nguyen Tuan Male 4/11/2000
Last edited on
Topic archived. No new replies allowed.