'I dont get how to read in from a file that has commas and spaces between words. I was told I could use the find and substr functions, I dont know em

This is the feedback I received but I don't know how to do it.

Since each line has commas as separator, then one way to read each line is to locate the first comma by find function, then get first data item using the substr function. Then locate the next comma using again the find function, and then get the second piece of data using the substr function, ... etc. Until you reach the end of the line, then get the next line and do the same processing in a while loop.

The text file:

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

Thats the question. Here is what I've done.

#include<fstream> 
#include<iostream> 
#include<string> 
using namespace std; 

const string FILE_NAME = "text.txt"; 

> this is the Business Structure 
struct Business 
{         
  string id;     
  string name;
  string address;
  string city;
  string state;
  string zip;
  double distance;  
  
  string get_id()        
  {               
      string tmp_id = id;                   
  }       
  
  > prints a line of business data.
  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(); 
  
}

> I think im messing up most here in the main function.
int main() 
{        
  
  ifstream inFile(FILE_NAME);     
  
  > Counting number of lines in the file         
  string line;    
  int lineCount = 0;      
  
  while (getline(inFile, line))           
      lineCount++;
      cout << correct;  
  
  >Going back to the start of file as we are at the EOF  
  inFile.close();    
  inFile.open(FILE_NAME);   
  
  Business *businesses = new Business[lineCount];     
  
  int index = 0;  
  
  > main part im having trouble with.

  while (!inFile.eof())   
  {               
      Business bs;           
      
      inFile >> bs.id;              
      inFile >> bs.name;  
      inFile >> bs.address;                 
      inFile >> bs.city;                   
      inFile >> bs.state;                           
      inFile >> bs.zip;                    
      inFile >> bs.distance;     
        
      businesses[index++] = bs;       
      
  }       
  
  inFile.close();        


  > Displays the menu and gives a result depending 
  
  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, lineCount);                  
                  break;          
              case 2:                         
                  cout << "Enter city name: ";                      
                  cin >> c_name;                    
                  SearchByCity(businesses, lineCount, c_name);                         
                  break;          
              case 3:                         
                  SortByDistanceAndSaveToFile(businesses, lineCount);                        
                  break;          
              
              default:                        
                  cout << "Invalid selction, try again...";                         
                  break;          
              
          }               
          cout << endl;     
          
      }
      return 0;
  }


Solution 1:[1]

Let's just take your feedback, use std::basic_string::find to locate the next delimiter ", " in the line you read with getline() and then use std::basic_string::substr to extract characters from the curent position to the position returned by std::basic_string::substr.

std::basic_string::find will return the position in the string where the first character of delimiter is found. You will need to keep 3 size_t variables (really 2 if you want to use the subtraction expression in substr). But for clarity we will use 3, pos the current position in the string, endpos for the location of the next delimiter, and count the number of characters to extract with substr between pos and endpos. (we will call the std::string holding the current line line) Let's label the open in-stream from the file is.

To read the entire line from the file you can do:

const std::string DELIM {", "};
...
  std::string line{};
  size_t count = 0, pos = 0, endpos = 0;;
  
  if (!getline (is, line)) {    /* read line validate stream state */
    return is;
  }
  
  /* locate the next delimiter position in line with substr, validate */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);  /* set failbit for return */
    return is;
  }
  count = endpos - pos;               /* compute no. of chars to extract */
  b.id = line.substr (pos, count);    /* extract id with substr */
  
  pos = endpos + DELIM.length();      /* update pos to after delimiter */

Since name, address, city, state, and zip are simply extracting with substr in exactly the same way, just duplicate the code to find, extract with substr and update pos for each of the strings.

distance is a bit different for two reasons. (1) there is no delimiter ", " to find after it (it's at the end of the line) and (2) it is a double values so you will use std::stod to convert the value at the end of the string to double. You can do that with a proper try {} catch() {} block to validate the conversion. You also should make sure there are characters after zip to convert before attempting the conversion. Which you can do by simply ensuring count is 1 or more by subtracting pos from line.length().

The block for extracting distance could be:

  count = line.length() - pos;    /* chars remaining in line */
  
  if (count < 1) {   /* validate chars remain for distance */
    std::cerr << "error: distance not available.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  
  try { /* convert remaining chars for distance */
    b.distance = stod (line.substr (pos, count)); 
  }
  catch(std::exception const& e) {  /* handle any error in conversion */
    std::cerr << "exception::what(): " << e.what() << '\n';
    is.setstate(std::ios_base::failbit);
  }

Why The Choice of is For Input Stream?

C++ allows you to overload the >> and << operators which you can do as friend functions to your struct. By convention the overload of >> uses is for the in-stream and << uses os for the out-stream. This allows you to write your struct and provide the ability to fill the fields from any stream holding the formatted data you want to read simply by using >> on the open stream just as you would do reading any variable from std::cin. Your struct definition with the overloads necessary to fill and output the struct becomes:

struct Business {         
  std::string id,
              name,
              address,
              city,
              state,
              zip;
  double distance;
  
  friend std::istream& operator >> (std::istream& is, Business& b);
  friend std::ostream& operator << (std::ostream& os, const Business& b);
};

Above the overload of >> will fill the struct members and the overload of << will output the struct members in whatever format you write in the << overload function. For the output example you can do:

std::ostream& operator << (std::ostream& os, const Business& b)
{
  os << "\nid      : " << b.id <<
        "\nname    : " << b.name <<
        "\naddress : " << b.address <<
        "\ncity    : " << b.city <<
        "\nstate   : " << b.state <<
        "\nzip     : " << b.zip <<
        "\ndistance: " << b.distance << '\n';
  
  return os;
}

The overload for >> is simply one find and substring and update of pos for each field to extract. For distance, you use the remaining characters in the line after zip and pass the string to stod() for conversion to double. It looks long and complicated, but really it's just the same thing repeated six-times to get trough zip and then just passing the characters that remain to stod(), e.g.

std::istream& operator >> (std::istream& is, Business& b)
{
  std::string line{};
  size_t count = 0, pos = 0, endpos = 0;;
  
  if (!getline (is, line)) {    /* read line validate stream state */
    return is;
  }
  
  /* locate the next delimiter position in line with substr, validate */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);  /* set failbit for return */
    return is;
  }
  count = endpos - pos;               /* compute no. of chars to extract */
  b.id = line.substr (pos, count);    /* extract id with substr */
  
  pos = endpos + DELIM.length();      /* update pos to after delimiter */
  
  /* same thing for name */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.name = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  /* same thing for address */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.address = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  /* same thing for city */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.city = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  /* same thing for state */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.state = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  /* same thing for zip */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.zip = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  count = line.length() - pos;    /* chars remaining in line */
  
  if (count < 1) {   /* validate chars remain for distance */
    std::cerr << "error: distance not available.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  
  try { /* convert remaining chars for distance */
    b.distance = stod (line.substr (pos, count)); 
  }
  catch(std::exception const& e) {  /* handle any error in conversion */
    std::cerr << "exception::what(): " << e.what() << '\n';
    is.setstate(std::ios_base::failbit);
  }
  
  return is;
}

A short example program taking the filename to read data from as the first argument, you can do something like:

int main (int argc, char **argv) {

  if (argc < 2) { /* validate at least 1 argument for filename */
    std::cerr << "usage: " << argv[0] << " filename\n";
    return 1;
  }
  
  Business tmp{};
  std::vector<Business> business{};   /* vector of struct */
  std::ifstream f (argv[1]);          /* open file stream */

  if (!f.good()) { /* validate file stream state good */
    std::cerr << "error: file open failed.\n";
    return 1;
  }
  
  while ((f >> tmp)) {            /* loop filling the tmp struct */
    business.push_back (tmp);     /* add it to your vector on success */
  }
  
  for (const auto& b : business) {  /* output each business in vector */
    std::cout << b;
  }
}

Putting it altogether you would have:

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

const std::string DELIM {", "};

struct Business {         
  std::string id,
              name,
              address,
              city,
              state,
              zip;
  double distance;
  
  friend std::istream& operator >> (std::istream& is, Business& b);
  friend std::ostream& operator << (std::ostream& os, const Business& b);
};

std::istream& operator >> (std::istream& is, Business& b)
{
  std::string line{};
  size_t count = 0, pos = 0, endpos = 0;;
  
  if (!getline (is, line)) {    /* read line validate stream state */
    return is;
  }
  
  /* locate the next delimiter position in line with substr, validate */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);  /* set failbit for return */
    return is;
  }
  count = endpos - pos;               /* compute no. of chars to extract */
  b.id = line.substr (pos, count);    /* extract id with substr */
  
  pos = endpos + DELIM.length();      /* update pos to after delimiter */
  
  /* same thing for name */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.name = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  /* same thing for address */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.address = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  /* same thing for city */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.city = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  /* same thing for state */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.state = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  /* same thing for zip */
  if ((endpos = line.find (DELIM, pos)) == std::string::npos) {
    std::cerr << "error: delimiter not found.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  count = endpos - pos;
  b.zip = line.substr (pos, count);
  
  pos = endpos + DELIM.length();
  
  count = line.length() - pos;    /* chars remaining in line */
  
  if (count < 1) {   /* validate chars remain for distance */
    std::cerr << "error: distance not available.\n";
    is.setstate(std::ios_base::failbit);
    return is;
  }
  
  try { /* convert remaining chars for distance */
    b.distance = stod (line.substr (pos, count)); 
  }
  catch(std::exception const& e) {  /* handle any error in conversion */
    std::cerr << "exception::what(): " << e.what() << '\n';
    is.setstate(std::ios_base::failbit);
  }
  
  return is;
}

std::ostream& operator << (std::ostream& os, const Business& b)
{
  os << "\nid      : " << b.id <<
        "\nname    : " << b.name <<
        "\naddress : " << b.address <<
        "\ncity    : " << b.city <<
        "\nstate   : " << b.state <<
        "\nzip     : " << b.zip <<
        "\ndistance: " << b.distance << '\n';
  
  return os;
}

int main (int argc, char **argv) {

  if (argc < 2) { /* validate at least 1 argument for filename */
    std::cerr << "usage: " << argv[0] << " filename\n";
    return 1;
  }
  
  Business tmp{};
  std::vector<Business> business{};   /* vector of struct */
  std::ifstream f (argv[1]);          /* open file stream */

  if (!f.good()) { /* validate file stream state good */
    std::cerr << "error: file open failed.\n";
    return 1;
  }
  
  while ((f >> tmp)) {            /* loop filling the tmp struct */
    business.push_back (tmp);     /* add it to your vector on success */
  }
  
  for (const auto& b : business) {  /* output each business in vector */
    std::cout << b;
  }
}

Example Use/Output

With your data in the file dat/business.txt, running the program and providing the file to read as the first command line argument, would produce:

$ ./bin/business dat/business.txt

id      : 2415
name    : Target Corporation
address : 3400 Green Mt Crossing Dr
city    : Shiloh
state   : IL
zip     : 62269
distance: 5.7

id      : 1705
name    : Starbucks
address : 1411 W Hwy 50
city    : O'Fallon
state   : IL
zip     : 62269
distance: 6.4

id      : 3218
name    : ALDI
address : 1708 N Illinois St
city    : Swansea
state   : IL
zip     : 62226
distance: 0.9

id      : 4062
name    : Walmart Supercenter
address : 2608 Green Mt Commons Dr
city    : Belleville
state   : IL
zip     : 62221
distance: 4

id      : 2011
name    : Spectrum Store
address : 3950 Green Mt Crossing Dr
city    : Shiloh
state   : IL
zip     : 62269
distance: 5.4

id      : 912
name    : Marco's Pizza
address : 1838 Central Plaza Dr
city    : Belleville
state   : IL
zip     : 62221
distance: 1.8

Look things over and let me know if you have questions.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 David C. Rankin