Brent Mayes
Brent Mayes

Reputation: 3

Is there any easy way to read a line from a file, split the first text part into a string, then split the last number part into a float?

I have an issue that I haven't been able to find a good way to solve, mostly because I am relatively new to C++, but not new to programming. I have a file with several lines in it, one of them being:

Plain Egg 1.45

I need to be able to read that line and split the first part, "Plain Egg", into a string, and then the last part, 1.45, into a float, and then do that for the rest of the lines. The following is some code I have so far, but I have been having an issue where it refuses to even read the file for some reason:

string line;
ifstream menuFile("menu.txt");
if (menuFile.is_open())
{
    int i = 0;
    while (getline(menuFile, line));
    {
        cout << line << endl;

        istringstream iss(line);

        iss >> dataList[i].menuItem >> dataList[i].menuPrice;
        /*iss >> dataList[i].menuPrice;*/
        i++;
    }

}
else
{
    cout << "Unable to open file.";
}

When I run it, it doesn't spit out "Unable to open file.", and when I trace it, it does enter the if loop, but it just doesn't read it. Besides that problem though, I want to know if this code would work in the way I want it to, and if doesn't, how to solve this problem.

EDIT: When I run it, it outputs what the last line of the file said, that being "Tea 0.75". The full file is as follows:

Plain Egg 1.45
Bacon and Egg 2.45
Muffin 0.99
French Toast 1.99
Fruit Basket 2.49
Cereal 0.69
Coffee 0.50 
Tea 0.75

EDIT 2: For some reason, the following code goes straight to the last line, Tea 0.75, and I have no idea why, shouldn't the getline just go line by line until the last line(?):

string line;
int index;
ifstream menuFile("menu.txt");
if (menuFile.is_open())
{
    while (getline(menuFile, line));
    {
        cout << line << endl;
        index = line.find_last_of(' ');
        cout << index << endl;
    }

}

EDIT 3: Above code has a semicolon at the end of the while loop, no wonder it was just ending on the last line, ughhh.

Upvotes: 0

Views: 375

Answers (3)

rturrado
rturrado

Reputation: 8064

  • Grab the line into a string.
  • Get the position of the last separator.
  • Your text is a substring of the line until the position of the separator.
  • Your number is a substring of the line from the position of the separator. You'll need to convert it to double first (and you should check for errors).

[Demo]

#include <iostream>  // cout
#include <string>  // find_last_of, getline, stod

int main()
{
    std::string line{};
    while (std::getline(std::cin, line))
    {
        auto pos{line.find_last_of(' ')};
        auto text{line.substr(0, pos)};
        auto number{std::stod(line.substr(pos))};
        std::cout << "text = " << text << ", number = " << number << "\n";
    }
}

// Outputs
//
//   text = Plain Egg, number = 1.45
//   text = Bacon and Egg, number = 2.45
//   text = Muffin, number = 0.99
//   text = French Toast, number = 1.99
//   text = Fruit Basket, number = 2.49
//   text = Cereal, number = 0.69
//   text = Coffee, number = 0.5
//   text = Tea, number = 0.75
//   

A more robust solution taking into account @Dúthomhas' comments:

  • Trims the right hand side of the string before finding the last separator.
  • Catches std::stod exceptions.

This solution detects:

  • Blank lines.
  • Lines without texts.
  • Lines without numbers.
  • Incorrect number formats.

[Demo]

#include <boost/algorithm/string.hpp>
#include <fmt/core.h>
#include <iostream>  // cout
#include <string>  // find_last_of, getline, stod

int main()
{
    std::string line{};
    while (std::getline(std::cin, line))
    {
        try
        {
            boost::trim_right(line);
            auto pos{line.find_last_of(' ')};
            auto text{line.substr(0, pos)};
            auto number{std::stod(line.substr(pos))};
            std::cout << "text = " << text << ", number = " << number << "\n";
        }
        catch (const std::exception&)
        {
            std::cout << fmt::format("* Error: invalid line '{}'\n", line);
        }
    }
}

// Outputs:
//
//   text = Plain Egg, number = 1.45
//   text = Bacon and Egg, number = 2.45
//   text = Muffin, number = 0.99
//   text = French Toast, number = 1.99
//   * Error: invalid line ''
//   * Error: invalid line 'Fruit Basket'
//   * Error: invalid line '0.75'
//   * Error: invalid line 'Coffee blah'

Upvotes: 3

uuu777
uuu777

Reputation: 901

Somewhat heavy handed: using the same idea of looking for the last space in string but with trimming spaces left and right. The bad thing is that while it is a lot of code and it still would not work with unicode in any form.

  // Right trim the line                                                                                                                                                                                                                                     
  while(!line.empty()) {
    if (isspace(line.back())) {
      line.pop_back();
    }
  }

  // Find the last space                                                                                                                                                                                                                                     
  size_t pos = line.find_last_of(" \t");
  if (pos == std::string::npos) {
    // Bad line: no spaces or only spaces                                                                                                                                                                                                                    
    handle_it();
  }

  // Get the price                                                                                                                                                                                                                                           
  double price = 0.0;
  try {
    size_t end_pos = 0;
    price = std::stod(line.substr(pos), &end_pos);
    if ((pos + end_pos) != line.length()) {
        // Another bad format: garbage at the end                                                                                                                                                                                                            
        handle_it();
      }
  } catch (...) {
    // Another bad format                                                                                                                                                                                                                                    
    handle_it();
  }

  // Left trim the item                                                                                                                                                                                                                                      
  size_t skip = 0;
  while(skip > pos && isspace(line[skip])) {
    skip++;
  }
  if (skip == pos) {
    // Another bad format: spaces + price                                                                                                                                                                                                                    
    handle_it();
  }

  // Right trim the item                                                                                                                                                                                                                                     
  // we know that we have at leas one non-space                                                                                                                                                                                                              
  pos--;
  while(isspace(line[pos])) {
    pos--;
  }

  std::string item = line.substr(skip, pos + 1);

Upvotes: 0

MFerguson
MFerguson

Reputation: 1747

You can do this for your given inputs by iterating the string to find the index at which the float value begins. Then grab the substring for each part and cast the value to a float.

#include <iostream>

using namespace std;

int main()
{
    string s = "Bacon and Egg 2.45";
    int l = 0;
    string food;
    float val = 0.0;
    
    for (int i = 0; i < s.length(); i++)
    {
        if(isdigit(s[i]))
        {
            l = i;
            break;
        }
    }
    
    food = s.substr(0,l);
    val = stof(s.substr(l,s.length()));
    cout << food << endl;
    cout << val;
}

Upvotes: 0

Related Questions