Using structs and Reading from file

I am creating a simple address book that reads a file and allows user to search by name, phone, or address. My code is not outputting the correct results. The program reads only the information from the first person in the address book and has an infinite loop. How can I fix my code?

Test File

Susan, Smith, (123)456-789
101 Main Street
Bob Smith, (567)345-9076,
456 Market Street

Header file
1
2
3
4
5
6
7
8
9
10
11
12
  #include <string>
using namespace std;
struct Person
{
    string firstname;
    string lastname;
};
struct AddressBook
{
    string phone;
    string address;
};


Program 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
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
int main(){

    Person person;
    AddressBook book;
    Search searchBook;

    string firstName, lastName, address, phone;
    string filename;
    ifstream inData;
    char searchOption;

    OpenFile(inData, filename);

    do {
        cout << "Enter how you want to search... " << endl;
        cout << "(F)irst name, (L)ast name, (A)ddress, (P)hone, e(X)it: ";
        cin >> searchOption;
        searchOption = toupper(searchOption);

        switch (searchOption) {
            case 'F':
                SearchFirstName(inData);
                break;
            case 'L':
                SearchLastName(inData);
                break;
            case 'A':
                SearchAddress(inData);
                break;
            case 'P':
                SearchPhone(inData);
                break;
            case 'X':
                break;
            default:
                cout << "Invalid option selected!" << endl;
                break;
        }

    } while (searchOption != 'X');

    return 0;
}

void OpenFile(ifstream& inData, string filename) {

    do {
        cout << "Enter file name to open: ";
        cin >> filename;

        inData.open(filename.c_str());

        if (!inData)
            cout << "File not found!" << endl;

    } while (!inData);
}

void SearchFirstName(ifstream& inData)
{
    Person p;

string firstName, lastName, phone, address;

cout << "Enter the first name of the person: ";
cin >> p.firstname;

string upperFirst = NormalizeString(p.firstname);
string upperSearchFirst = NormalizeString(firstName);
while (GetRecord(inData, firstName, lastName, phone, address))
{

if (upperFirst == upperSearchFirst)
 break;

if(inData){
cout << "Person found: " << endl;
PrintRecord(firstName,lastName, phone, address);
}
else
{
cout << p.firstname << " not found!" << endl << endl;
}

inData.clear();
inData.seekg(0);
}
}


void SearchLastName(ifstream& inData)
{
    Person p;

string firstName, lastName, phone, address;

cout << "Enter the last name of the person: ";
cin >> p.lastname;

string upperLast = NormalizeString(p.lastname);

while (GetRecord(inData, firstName, lastName, phone, address))
{
string upperSearchLast = NormalizeString(lastName);
if(upperLast==upperSearchLast)
    break;

if (inData)
{
cout << "Person found: " << endl;
PrintRecord(firstName,lastName, phone, address);
}
else
{
cout << lastName << " not found!" << endl << endl;
}

inData.clear();
inData.seekg(0);
}
}

// Not implemented. (Similar to SearchFirstName)
void SearchAddress(ifstream& inData)
{
    AddressBook book;

string firstName, lastName, phone, address;

cout << "Enter the address of the person: ";
cin >> book.address;

string upperAdd = NormalizeString(book.address);

while (GetRecord(inData, firstName, lastName, phone, address))
{
string upperSearchAdd = NormalizeString(address);
if (upperAdd == upperSearchAdd)
    break;

if (inData)
{
cout << "Person found: " << endl;
PrintRecord(firstName,lastName, phone, address);
}
else
{
cout << book.address<< " not found!" << endl << endl;
}

inData.clear();
inData.seekg(0);
}
}


// Not implemented. (Similar to SearchFirstName)
void SearchPhone(ifstream& inData)
{
    AddressBook book;

string firstName, lastName, phone, address;

cout << "Enter the phone number of the person: ";
cin >> book.phone;



while (GetRecord(inData, firstName, lastName, phone, address))
{
if (book.phone == phone)
    break;

if(inData){
cout << "Person found: " << endl;
PrintRecord(firstName,lastName, book.phone, address);
}
else
{
cout << phone<< " not found!" << endl << endl;
}

inData.clear();
inData.seekg(0);
}
}


// Read all elements of an address book entry from a given file
// Return true if successful, false othewise (Based on stream state)
bool GetRecord(ifstream& inData,
               string& firstName, string& lastName,
               string& phone, string& address) {


    getline(inData, firstName, ',');

    getline(inData, lastName, ',');

    getline(inData, phone, '\n');

    getline(inData, address);
    return inData;

}

// Given all the elements of an address book entry, print the results
void PrintRecord(string firstName, string lastName,string phone, string address)
 {
    cout << "Name: " << firstName << " " << lastName << endl;
    cout << "Phone: " << phone << endl;
    cout << "Address: " << address << endl;
}

// Convert passed string to all upper case and return result
string NormalizeString(string str) {

    string nString = str;
    int i;

    for (i = 0; i < str.size(); i++)
    {
        nString[i] = toupper(str[i]);
    }

    return nString;
}
Last edited on
Hello tayloras96,

Yes there are many problems with the way you are doing things. Starting at the top.

The text file. Based on the way you have written the GetRecord() function the file is not set up correctly. I would suggest:

Susan,Smith,(123)456-789
101 Main Street
Bob,Smith,(567)345-9076
456 Market Street

or

Susan,Smith,(123)456-789,101 Main Street
Bob,Smith,(567)345-9076,456 Market Street
notice the comma after the first name and the lack of space after the commas. This will fit the way the GetRecord() function is written. More on the function later.

In your header file the #include and using line are not needed. Actually you should not need anything like these lines in a header file.

I do not see anything wrong with the "main()" function although I have not tested the program yet.

In the "GetRecord()" function I do see some problems. line 95 firstName = p.firstname; assignment is right to left, so "firstName" is being assigned the value of ".pfirstName" which has no value. it it backwards. Line 97 is to read until a comma is reached, but that come after last name, so "firstName" would contain first and last names and "lastName" would contain the phone number and maybe everything up to the next comma. Throwing everything off with you reads. Note the section on changing the layout of the text file. The function "TrimSpaces()" should not be needed if the input file is setup right.

There is something to start with until I have a better understanding of how the program works.

Hope that helps for now,

Andy

P.S. forgot to mention the "GetRecord()" returns a bool yet you return a stream. This will not work.
Last edited on
Handy Andy,

Thanks for your help I was able to fix part of the GetRecord function. My code has an infinite loop and I'm not sure how to fix it.
Hi @tayloras96,

If this is a personal project (and not an assignment) i would suggest starting by:

1.- Create a record struct or class
2.- Load records from file into a record container (std::vector, array, etc)
3.- Perform searches,... ,etc

This is a little test to show my point:

Following @Handy Andy suggestion for record syntax

(The number and symbol on the left are not part of the records)

records.txt



1| Susan,Smith,(123)456-789,101 Main Street
2|
3| Bob,Smith,(567)345-9076,456 Market Street
4| Enrique,Hernández,(123) 456-789, 7 San José Street



Console output



Person found: Bob

First name: Susan
Last name: Smith
Phone: (123)456-789
Address: 101 Main Street

First name: Bob
Last name: Smith
Phone: (567)345-9076
Address: 456 Market Street

First name: Enrique
Last name: Hernández
Phone: (123) 456-789
Address:  7 San José Street



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


struct Record {
    
    std::string firstName;
    std::string lastName;
    std::string phone;
    std::string address;
    
};


std::vector<Record> loadRecords(std::string& fileName);

void printRecords(const std::vector<Record>& records);

bool searchFirstName(const std::vector<Record>& records, const std::string& firstName);

int main(int argc, const char * argv[]) {
    
    std::string file_name = "/Users/Eyenrique/Documents/test01/records.txt";
    
    std::vector<Record> Records = loadRecords(file_name);
    
    std::string firstName= "Bob";
    
    if(!searchFirstName(Records, firstName))
    {
        std::cout<<firstName <<" not found!\n";
    }
    
    printRecords(Records);

    return 0;
}


std::vector<Record> loadRecords(std::string& fileName)
{
    std::vector<Record> records;
    
    std::ifstream ifile(fileName);
    
    if(ifile)
    {
        std::string record = "";
        
        while(std::getline(ifile,record))
        {
            //This is to skip the empty last line from:
            //"std::getline" or an empty record between records
            if(record.size() == 0) continue;
            
            int counter = 1;
            Record rholder;
            
            while(record.size() > 0)
            {
                size_t index = record.find(','); //string[,] <- end index
               // if: there is no more ',' comas
              //our end index will be stringrecord[] <- end of the string
                if(index == std::string::npos) index = record.size();
              //Extracting desired info
                std::string holder = record.substr(0,index);
              //Erasing desired info + ',' character from our 'stringrecord'
             //and decrementing 'record.size()' variable
            //when record.size() == 0 the while loop will be skipped
                record.erase(0,index+1);

            //This is the non-fancy part
           //due to the lack of 'record labels' : "[{name:"Some name"},{age:18}]"
          //we need to do something like this (i guess)
                if(counter == 1) rholder.firstName = holder;
                if(counter == 2) rholder.lastName = holder;
                if(counter == 3) rholder.phone = holder;
                if(counter == 4) rholder.address = holder;
                
                ++counter;
            }
            
            records.push_back(rholder);
            
            
        }
    }else
    {
        std::cout<<"We can't open the file: " <<"\"" <<fileName <<"\"\n";
    }
    
    return records;
}

bool searchFirstName(const std::vector<Record>& records, const std::string& firstName)
{
    bool found = false;
    
    std::string key = firstName;
    
    std::transform(key.begin(), key.end(), key.begin(), ::toupper);
    
    for(const auto record : records)
    {
        std::string fn = record.firstName;
        std::transform(fn.begin(), fn.end(), fn.begin(), ::toupper);
        
        if(key.compare(fn) == 0)
        {
            found = true;
            std::cout<<"Person found: " <<record.firstName <<"\n\n";
        }
    }
    
    return found;
}

void printRecords(const std::vector<Record>& records)
{
    for(const auto record : records)
    {
        std::cout<<"First name: " <<record.firstName <<"\nLast name: " <<record.lastName
        <<"\nPhone: " <<record.phone <<"\nAddress: " <<record.address <<"\n\n";
    }
}


If you are interested in a C++ class that deals with record's "labels" send me an email,
i remember that i wrote one some time ago...
regards!
Hello tayloras96,

Your infinite loop come from line 76 if (inData) . The while loop causes a read of the file, but when you reach the end of file the stream fails, so when you get to the if statement it will always be false and else will always print before going back to the while loop to do it all over again. Because there is no way out of the loop it never stops.

I have noticed many problems or things that have been done backwards. Parts of your program are worth keeping like "NormalizeString" and "PrintRecord" I believe each function works although I have not fully tested each yet.

@eyenrique has said the same as I would have. Instead of trying to read the file each time you do a search you need to read the file once, put the information into an instance of the structs and then store that in a std::vector or some other type of container. Then you can loop through the vector instead of reading the file each time.

I can work with what you have and make it work, but it would no be pretty. Some thought before you rush into coding will make a big difference.

Hope that helps,

Andy
Thanks for all of your help!
Topic archived. No new replies allowed.