Pratap Biswakarma
Pratap Biswakarma

Reputation: 117

How to create a function to read from a text file in C++ and ignore certain character and store data values in appropriate variables?

I have the class date defined as follows. I have programed the function write(ostream &o) to write the date in this format 27/May/2020 so its easily readable to the user. Now i want to read from a file containing the date in aforementioned format. So how do i write a function in C++ that would ignore the '/' character and store the value 27 for example in int day, the value "May" in string month and the value 2010 in int year. Also how can i design a function to ignore a certain character and be able to store the values in various data types in C++?

class date{
    private :
        int day;
        string month;
        int year;

        public:
            date()
            {
                day=0;
                month="NULL";
                year=2020;
            }

            void getdate()
            {
                cout<<"Enter Date : ";
                cin>>day>>month;
            }

            void write(ostream &o) const  // to write text file or screen
            {
                o<<day<<'/'<<month.c_str()<<'/'<<year;
            }

};

Upvotes: 0

Views: 515

Answers (1)

A M
A M

Reputation: 15277

You could use a very simple approach, using the property of stream extraction/insertion operators return a reference to the stream. With that you can chain extraction statement like in your example cin>>day>>month;.

With that you can do a one liner, like in the following example:

#include <iostream>
#include <string>
#include <sstream>

std::istringstream iss{"27/May/2020"};

int main() {

    int day{};
    std::string month{};
    int year{};
    char slash{};

    if ((std::getline(iss >> day >> slash, month, '/') >> year) && (slash == '/')) {

        std::cout << day << ' ' << month << ' ' << year << '\n';
    }
    else {
        std::cerr << "\nError: Wrong input format\n";
    }
    return 0;
}

EDIT

OP asked for more explanation for the one liner.

if ((std::getline(iss >> day >> slash, month, '/') >> year) && (slash == '/')) {

OK. As you know, nested functions will be evaluated from inside out. If you have for example: pow(sqrt(x+100),y+2), then at first x+100, then sqrt then y+2 and finally pow will be evaluated. Good, understood.

Looking at our one liner, it means, first iss >> day will be evaluated. In the open stream iss (you can also use std::cin or an std::ifstream or basically any istream) we have at the beginning: "27/May/2020". The above extraction operation will extract the digits 2 and 7 and convert it to the integer 27. That is our day.

Now, the input stream contains "/May/2020" because the 2 and the 7 have already been extracted.

The operation iss >> day is finished now. The overloaded extractor operator >> will always return a reference to the istream with which it was invoked. Meaning, iss >> day will return "iss".

The prototype of such a operator is usually defined as

std::istream& operator >> (std::istream& is, SomeClass& sc) { 
    // Do something with SomeClass sc . . .
    return is; }

You can see, that the stream is returned. And that gives you the possibility to chain the extractor operator. After this first iss >> day statement has been evaluated, the rest of the line would look like:

 if ((std::getline(iss >> slash, month, '/') >> year) && (slash == '/')) 

because iss >> day has been done and returned iss. Remember that our stream contains "/May/2020" now. And, next we will do: iss >> slash. Meaning, we will extract the slash from the stream and store it in the variable slash. The extractor operator will again return iss. And the data in the stream is now "May/2020". The new one liner will be:

if ((std::getline(iss, month, '/') >> year) && (slash == '/'))

This we can understand. std::getline will extract a string from the stream, until it sees a slash. That string is stored in the variable month. The data in the stream will now be "2020". And guest what?std::getline```` will also return a reference to the given stream. With that the one liner will now be

 if ((iss >> year) && (slash == '/'))

We know, what will come now: iss >> year will be evaluated. "2020" will be converted to the integer 2020. The stream is now empty. Everything has been extracted. The operator >>will again return iss. Giving us:

if ((iss) && (slash == '/'))

Hm, if expects a boolean expression, how does this work with iss? Can be explained: If expects a boolean expression and, very smart, a stream as an overloaded bool operator. Please read here. And this operator returns, if the stream is in good state or failed.

If some extraction operation in the whole chain would have failed, then the bool operator would return false. And then we would show the error message.

As an additional check, we validate the contents of the variable "slash".

So, that's it. I hope, i could explain it in an understandable way.


End EDIT


But maybe it is more safe, if you first read a complete line (std::string with std::getline and then split this line.

I will show you some example code for this as well.

See some common patterns for splitting a string:

Splitting a string into tokens is a very old task. There are many many solutions available. All have different properties. Some are difficult to understand, some are hard to develop, some are more complex, slower or faster or more flexible or not.

Alternatives

  1. Handcrafted, many variants, using pointers or iterators, maybe hard to develop and error prone.
  2. Using old style std::strtok function. Maybe unsafe. Maybe should not be used any longer
  3. std::getline. Most used implementation. But actually a "misuse" and not so flexible
  4. Using dedicated modern function, specifically developed for this purpose, most flexible and good fitting into the STL environment and algortithm landscape. But slower.

Please see 4 examples in one piece of code.

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <regex>
#include <algorithm>
#include <iterator>
#include <cstring>
#include <forward_list>
#include <deque>

using Container = std::vector<std::string>;
std::regex delimiter{ "," };


int main() {

    // Some function to print the contents of an STL container
    auto print = [](const auto& container) -> void { std::copy(container.begin(), container.end(),
        std::ostream_iterator<std::decay<decltype(*container.begin())>::type>(std::cout, " ")); std::cout << '\n'; };

    // Example 1:   Handcrafted -------------------------------------------------------------------------
    {
        // Our string that we want to split
        std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
        Container c{};

        // Search for comma, then take the part and add to the result
        for (size_t i{ 0U }, startpos{ 0U }; i <= stringToSplit.size(); ++i) {

            // So, if there is a comma or the end of the string
            if ((stringToSplit[i] == ',') || (i == (stringToSplit.size()))) {

                // Copy substring
                c.push_back(stringToSplit.substr(startpos, i - startpos));
                startpos = i + 1;
            }
        }
        print(c);
    }

    // Example 2:   Using very old strtok function ----------------------------------------------------------
    {
        // Our string that we want to split
        std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
        Container c{};

        // Split string into parts in a simple for loop
#pragma warning(suppress : 4996)
        for (char* token = std::strtok(const_cast<char*>(stringToSplit.data()), ","); token != nullptr; token = std::strtok(nullptr, ",")) {
            c.push_back(token);
        }

        print(c);
    }

    // Example 3:   Very often used std::getline with additional istringstream ------------------------------------------------
    {
        // Our string that we want to split
        std::string stringToSplit{ "aaa,bbb,ccc,ddd" };
        Container c{};

        // Put string in an std::istringstream
        std::istringstream iss{ stringToSplit };

        // Extract string parts in simple for loop
        for (std::string part{}; std::getline(iss, part, ','); c.push_back(part))
            ;

        print(c);
    }

    // Example 4:   Most flexible iterator solution  ------------------------------------------------

    {
        // Our string that we want to split
        std::string stringToSplit{ "aaa,bbb,ccc,ddd" };


        Container c(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});
        //
        // Everything done already with range constructor. No additional code needed.
        //

        print(c);


        // Works also with other containers in the same way
        std::forward_list<std::string> c2(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {});

        print(c2);

        // And works with algorithms
        std::deque<std::string> c3{};
        std::copy(std::sregex_token_iterator(stringToSplit.begin(), stringToSplit.end(), delimiter, -1), {}, std::back_inserter(c3));

        print(c3);
    }
    return 0;
}

Upvotes: 1

Related Questions