Reading data from text file, with commas, into a dynamic array of structs

I have no idea on how to read in this file, every method I try does not result in an output. the ret of the code works just fine the only part that doesn't work is reading the file within the while loop. how do I read in a file that has a string with space and commas separating the strings?

This is the content of the 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
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
2415, Target Corporation, 3400 Green Mt Crossing Dr, Shiloh, IL, 62269, 5.7
1705, Starbucks, 1411 W Hwy 50, O'Fallon, IL, 62269, 6.4
3218, ALDI, 1708 N Illinois St, Swansea, IL, 62226, 0.9
4062, Walmart Supercenter, 2608 Green Mt Commons Dr, Belleville, IL, 62221, 4.0
2011, Spectrum Store, 3950 Green Mt Crossing Dr, Shiloh, IL, 62269, 5.4
912, Marco's Pizza, 1838 Central Plaza Dr, Belleville, IL, 62221, 1.8
[code]

[code]
  #include<fstream> 
#include<iostream> 
#include<string> 
using namespace std; 

const string FILE_NAME = "text.txt"; 

struct Business 
{         
    string id;     
    string name;
    string address;
    string city;
    string state;
    string zip;
    double distance;  
    
    string get_id()        
    {               
        string tmp_id = id;           
        return tmp_id.replace(0, 1, "**");         
        
    }       
    
    void print()    
    {               
        cout << get_id() << ", " << name << ", " << address << ", " << state << ", " << zip << ", " << (distance * 1.6) << endl;   
        
    } 
    
}; 

void DisplayAllBusinessInfo(Business* businesses, int count) 
{     
    for (int i = 0; i < count; ++i)      
    {               
        businesses[i].print();   
    } 
    
} 

void SearchByCity(Business* businesses, int count, const string& c_name) 
{   
    for (int i = 0; i < count; ++i)      
    {               
        if (businesses[i].city.find(c_name) != string::npos)             
        {                       
            businesses[i].print();           
            
        }       
        
    } 
    
} 

void SortByDistanceAndSaveToFile(Business* businesses, int count) 
{    
      
    for (int i = 0; i < count - 1; ++i)  
    {               
        for (int j = i + 1; j < count; ++j)
        {
            if (businesses[j].distance < businesses[i].distance)                               
                auto tmp = businesses[i];        
        }
    }       
            
            // Writing Sorted data to the original file    
            ofstream outFile(FILE_NAME);    
            for (int i = 0; i < count; ++i)      
            {               
                outFile << businesses[i].id << " " << businesses[i].name << " " << businesses[i].address << " " << businesses[i].city << " " << businesses[i].state << " " << businesses[i].zip << " " << businesses[i].distance;            
                if (i < count - 1)                  
                    outFile << endl;  
                
            }       
            
            outFile.close(); 
    
}

int main() 
{
    int size;
    int count = 0;
    
    ifstream fin(FILE_NAME); 
    
    fin >> size;
    
    if (!fin)
    {
        cout << "Could not open file.\n";
        return 1;
    }
    
    fin.ignore(1000, '\n'); // ignore everything until end of line

    Business* businesses = new Business[size]; // Dynamic allocation of array

    // while array is not filled AND the file status is good
    // do the following:
    while (count < size && fin)
    {      
        getline(fin, businesses[count].id);
        getline(fin, businesses[count].name);
        getline(fin, businesses[count].address);
        getline(fin, businesses[count].city);
        getline(fin, businesses[count].state);
        getline(fin, businesses[count].zip);
        fin >> businesses[count].distance;
        fin.ignore(); // ignore a single character
        fin.ignore(",");
        
        if (fin)     // add 1 to count if the file access was successful
            count++;
    }

    fin.close();

    
    // Display menu         
    
    while (1)       
    {               
        cout << "1. Display all business' info" << endl;           
        cout << "2. Search by city" << endl;           
        cout << "3. Sort by distance" << endl;                
        cout << "4. Exit" << endl;          
        cout << endl << "Please pick a number: ";              
        
        int choice;             
        cin >> choice;            
        
        if (choice == 4) exit(0);               
        
        string c_name;          
        
        switch (choice)                 
        {               
            case 1:                         
                DisplayAllBusinessInfo(businesses, count);                  
                break;          
            case 2:                         
                cout << "Enter city name: ";                      
                cin >> c_name;                    
                SearchByCity(businesses, count, c_name);                         
                break;          
            case 3:                         
                SortByDistanceAndSaveToFile(businesses, count);                        
                break;          
            
            default:                        
                cout << "Invalid selction, try again...";                         
                break;          
            
        }               
        cout << endl;     
        
    }
    return 0;
}
You could pass a third argument to std::getline to tell it at which character to stop. By default it uses '\n' (the newline character).

This
 
getline(fin, businesses[count].id, ',')
will read all characters from fin up until the next comma and store them in businesses[count].id. The comma is discarded so the next read operation (e.g. getline or >>) will start reading after the comma.
Last edited on
This is a CSV file - so needs to be split on , and also to trim leading/trailing white space. As C++20, this will read the file and display the adjusted data:

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
#include <string>
#include <string_view>
#include <fstream>
#include <iostream>
#include <ranges>
#include <vector>
#include <charconv>

constexpr const char* FILE_NAME { "text.txt" };

struct Business {
	std::string id;
	std::string name;
	std::string address;
	std::string city;
	std::string state;
	std::string zip;
	double distance {};

	std::string get_id() const {
		std::string tmp_id {id};

		return tmp_id.replace(0, 1, "**");
	}
};

template<typename T = std::string_view>
auto split(const std::string_view str, const std::string_view delims = ",") {
	std::vector<T> output;

	for (const auto& s : std::ranges::split_view(str, delims)) {
		auto word { s | std::views::drop_while(isspace) | std::views::reverse | std::views::drop_while(isspace) | std::views::reverse };

		output.emplace_back(std::to_address(word.begin()), std::to_address(word.end()));
	}

	return output;
}

std::ostream& operator<<(std::ostream& os, const Business& bus) {
	return os << bus.get_id() << ", " << bus.name << ", " << bus.address << ", " << bus.city << ", " << bus.state << ", " << bus.zip << ", " << (bus.distance * 1.6);
}

std::istream& operator>>(std::istream& is, Business& bus) {
	std::string line;

	if (std::getline(is, line)) {
		const auto b {split(line)};

		bus.id = b[0];
		bus.name = b[1];
		bus.address = b[2];
		bus.city = b[3];
		bus.state = b[4];
		bus.zip = b[5];
		std::from_chars(b[6].data(), b[6].data() + b[6].size(), bus.distance);
	}

	return is;
}

int main() {
	std::ifstream fin(FILE_NAME);

	if (!fin)
		std::cout << "Could not open file.\n";

	std::vector<Business> bus;

	for (Business b; fin >> b; bus.emplace_back(std::move(b)));

	for (const auto& b : bus)
		std::cout << b << '\n';
}



**415, Target Corporation, 3400 Green Mt Crossing Dr, Shiloh, IL, 62269, 9.12
**705, Starbucks, 1411 W Hwy 50, O'Fallon, IL, 62269, 10.24
**218, ALDI, 1708 N Illinois St, Swansea, IL, 62226, 1.44
**062, Walmart Supercenter, 2608 Green Mt Commons Dr, Belleville, IL, 62221, 6.4
**011, Spectrum Store, 3950 Green Mt Crossing Dr, Shiloh, IL, 62269, 8.64
**12, Marco's Pizza, 1838 Central Plaza Dr, Belleville, IL, 62221, 2.88

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

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

struct Business 
{         
   string id;
   string name;
   string address;
   string city;
   string state;
   string zip;
   double distance;  
}; 

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

istream & operator >> ( istream &in, Business &b )
{     
   string line;
   getline( in, line );
   if ( in )
   {
      replace( line.begin(), line.end(), ',', '\n' );
      stringstream ss( line );
      getline( ss >> ws, b.id      );
      getline( ss >> ws, b.name    );
      getline( ss >> ws, b.address );
      getline( ss >> ws, b.city    );
      getline( ss >> ws, b.state   );
      getline( ss >> ws, b.zip     );
      ss >> b.distance;
   } 
   return in;
} 

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

ostream & operator << ( ostream &out, const Business &b )
{   
   #define SPACE << " | "
   return out << setw( 4) << b.id      SPACE
              << setw(20) << b.name    SPACE
              << setw(30) << b.address SPACE
              << setw(15) << b.city    SPACE
              << setw( 3) << b.state   SPACE
              << setw( 6) << b.zip     SPACE
              << b.distance;
}

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

int main() 
{
   const string FILE_NAME = "text.txt";
   ifstream fin( FILE_NAME );
   vector<Business> businesses;
   for ( Business b; fin >> b; ) businesses.push_back( b );
   for ( Business &b : businesses ) cout << b << '\n';
}


2415 |   Target Corporation |      3400 Green Mt Crossing Dr |          Shiloh |  IL |  62269 | 5.7
1705 |            Starbucks |                  1411 W Hwy 50 |        O'Fallon |  IL |  62269 | 6.4
3218 |                 ALDI |             1708 N Illinois St |         Swansea |  IL |  62226 | 0.9
4062 |  Walmart Supercenter |       2608 Green Mt Commons Dr |      Belleville |  IL |  62221 | 4
2011 |       Spectrum Store |      3950 Green Mt Crossing Dr |          Shiloh |  IL |  62269 | 5.4
 912 |        Marco's Pizza |          1838 Central Plaza Dr |      Belleville |  IL |  62221 | 1.8
Last edited on
Please be aware that a CSV file is a very special case of non-obvious complexity that will bite you when you least expect it.

For example, both of the above examples will fail on input with quoted fields containing commas.

Unfortunately there is no way around the complexities except:

• being absolutely clear in docs that the input NOT have, say, quoted fields with newlines or anything weird, and/or

• write a full DFA parser for your CSV.

Alas.
Agreed :) :)

A quoted field is quite easy to deal with, but a quoted field containing a delim is a pain in the *&^% - and don't get me started on things like new-line in field etc etc etc.
Perhaps std::quoted could help?

https://en.cppreference.com/w/cpp/io/manip/quoted
boost::tokenizer would certainly help.
https://www.boost.org/doc/libs/1_79_0/libs/tokenizer/doc/escaped_list_separator.htm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <string>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <iomanip>

int main()
{
    const std::string str = "2415, Target Corporation  ,   \"3400, Green Mt \n  Crossing Dr  \","
                            " Shiloh, IL, 62269, 5.7" ;
    std::cout << str << '\n' ;

    const boost::tokenizer< boost::escaped_list_separator<char> > tokeniser(str) ;

    int n = 0 ;
    for( std::string token : tokeniser )
    {
        boost::trim(token) ;
        std::cout << "\nfield #" << ++n << ". " << std::quoted(token) << '\n' ;
    }
}

http://coliru.stacked-crooked.com/a/759e866e4e1a6260
Last edited on
Line 19 could be modified using C++20's std::format:
std::cout << std::format("\nField #{}: \"{}\"\n", ++n, token);

Instead of #include <iomanip>, #include <format>.*

Same-o output.

Nice use of Boost, BTW. :)

*If one's compiler doesn't yet have <format> available there is the {fmt} library:
https://github.com/fmtlib/fmt/blob/master/README.rst
Helpless student wrote:
Reading data from text file, with commas, into a dynamic array of structs

Others have mentioned it, I'll be more explicit.

Instead of using a dynamic regular array requiring manual memory management use a C++ container. One to consider is std::vector, it is almost a direct exchange:
https://www.learncpp.com/cpp-tutorial/an-introduction-to-stdvector/

You get the advantage of a container that can be sized and resized at run-time, without worrying about all the messy details of manual memory management that can be all too easy to screw up.

You're passing your array into functions, with a vector you don't need to pass the size (number of stored elements) also, the vector "remembers" its size even when passed into a function.
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include<fstream>
#include<iostream>
#include<string>

using namespace std;

const string FILE_NAME = "text.txt";

struct Business
{
    string id;
    string name;
    string address;
    string city;
    string state;
    string zip;
    double distance;
    
    void print()
    {
        cout
        << id << ", " << name << ", " << address << ", " << state << ", "
        << zip << ", " << (distance * 1.6) << endl;
    }
};

void DisplayAllBusinessInfo(Business* businesses, int count)
{
    for (int i = 0; i < count; ++i)
    {
        businesses[i].print();
    }
}


int main()
{
    ifstream fin(FILE_NAME);
    if (!fin)
    {
        cout << "Could not open file.\n";
        exit(1);
    }
    
    // GET SIZE OF FILE
    string temp_line;
    int size{0};
    while( getline(fin, temp_line) )
    {
        size++;
    }
    cout << "There are " << size << " records.\n";
    
    // REWIND FILE
    fin.clear();
    fin.seekg(0);
    
    
    // LOAD UP THE ARRAY
    Business* businesses = new Business[size];
    Business temp;
    int count{0};
    while (count < size)
    {
        getline(fin, temp.id, ',');
        getline(fin, temp.name, ',');
        getline(fin, temp.address,',');
        
        getline(fin, temp.city,',');
        getline(fin, temp.state,',');
        getline(fin, temp.zip,',');
        
        getline(fin, temp_line);
        temp.distance = stod(temp_line);
        
        businesses[count] = temp;
        
        count++;
    }
    fin.close();
    
    DisplayAllBusinessInfo(businesses, count);
    
    return 0;
}


There are 6 records.
2415,  Target Corporation,  3400 Green Mt Crossing Dr,  IL,  62269, 9.12
1705,  Starbucks,  1411 W Hwy 50,  IL,  62269, 10.24
3218,  ALDI,  1708 N Illinois St,  IL,  62226, 1.44
4062,  Walmart Supercenter,  2608 Green Mt Commons Dr,  IL,  62221, 6.4
2011,  Spectrum Store,  3950 Green Mt Crossing Dr,  IL,  62269, 8.64
912,  Marco's Pizza,  1838 Central Plaza Dr,  IL,  62221, 2.88
Program ended with exit code: 0


PS <vector>'s or similar STL container overcome the need to count the number of records and rewinding the file - but it's no big deal. Searching and sorting, however, are - unless you have to write your own.
Last edited on
Topic archived. No new replies allowed.