using sstream to read a vector<string>

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • arnuld

    using sstream to read a vector<string>

    This works fine, I welcome any views/advices/coding-practices :)


    /* C++ Primer - 4/e
    *
    * Exercise 8.9
    * STATEMENT:
    * write a program to store each line from a file into a
    * vector<string>. Now, use istringstream to read read each line
    * from the vector a word at a time.
    *
    */

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


    void read_file( std::ifstream& infile, std::vector<std ::string>&
    svec )
    {
    std::string a_line;
    while( std::getline( infile, a_line ) )
    {
    svec.push_back( a_line );
    }
    }


    /* This program works nearly in 3 steps:

    step 1 : we take the path of the file and open it.
    step 2 : we read the file into the vector<string>
    ansd close it.
    step 3 : we use the istringstream to read each line,
    a word at a time.
    */
    int main()
    {
    /* step 1 */
    std::vector<std ::stringsvec;

    std::cout << "Enter full path of the file: ";
    std::string path_to_file;
    std::getline( std::cin, path_to_file );

    std::ifstream infile( path_to_file.c_ str() );

    /* step 2 */
    /* check whether file was even opened or not */
    if( infile.good() )
    {
    read_file( infile, svec );
    }

    /* reading finished, don't forget to close the file */
    infile.close();

    /* step 3 */
    for( std::vector<std ::string>::cons t_iterator iter = svec.begin();
    iter != svec.end(); ++iter)
    {
    std::string a_word;
    /* bind that line, to the istringstream */
    std::istringstr eam vector_line( *iter );

    while( vector_line >a_word )
    {
    std::cout << a_word << " ";
    }
    }

    std::cout << std::endl;

    return 0;
    }

    --------- OUTPUT ------------
    [arnuld@Arch64 c++]$ g++ -ansi -pedantic -Wall -Wextra ex_08-16.cpp
    [arnuld@Arch64 c++]$ ./a.out
    Enter full path of the file: /home/arnuld/programming/scratch.txt
    ;; This buffer is for notes you don't want to save, and for Lisp
    evaluation. ;; If you want to create a file, visit that file with C-x
    C-f, ;; then enter the text in that file's own buffer.
    [arnuld@Arch64 c++]$

  • Victor Bazarov

    #2
    Re: using sstream to read a vector&lt;strin g&gt;

    arnuld wrote:
    This works fine, I welcome any views/advices/coding-practices :)
    >
    >
    /* C++ Primer - 4/e
    *
    * Exercise 8.9
    * STATEMENT:
    * write a program to store each line from a file into a
    * vector<string>. Now, use istringstream to read read each line
    * from the vector a word at a time.
    *
    */
    >
    #include <iostream>
    #include <string>
    #include <vector>
    #include <fstream>
    #include <sstream>
    >
    >
    void read_file( std::ifstream& infile, std::vector<std ::string>&
    svec )
    {
    std::string a_line;
    while( std::getline( infile, a_line ) )
    {
    svec.push_back( a_line );
    }
    }
    Why limit this to 'ifstream'? Why not 'istream'? Then you could
    re-use it to read from the standard input as well.
    >
    >
    /* This program works nearly in 3 steps:
    >
    step 1 : we take the path of the file and open it.
    step 2 : we read the file into the vector<string>
    ansd close it.
    step 3 : we use the istringstream to read each line,
    a word at a time.
    */
    int main()
    {
    /* step 1 */
    std::vector<std ::stringsvec;
    >
    std::cout << "Enter full path of the file: ";
    std::string path_to_file;
    std::getline( std::cin, path_to_file );
    You should consider asking this question only if 'argv[1]' was not
    provided. If it is provided, try assigning it to 'path_to_file':

    if (argc 1)
    path_to_file = argv[1];
    else {
    std::cout << "Enter file name: " << std::flush;
    std::getline(st d::cin, path_to_file);
    }

    And check the error condition on the 'cin' here -- what if I press
    Ctrl-D. You should exit then.
    >
    std::ifstream infile( path_to_file.c_ str() );
    >
    /* step 2 */
    /* check whether file was even opened or not */
    if( infile.good() )
    {
    read_file( infile, svec );
    There is no checking whether you actually read anything, and there
    was no error in process (not sure if you want/need that, but might
    be useful to tell the user that reading failed in case it has).
    }
    >
    /* reading finished, don't forget to close the file */
    infile.close();
    >
    /* step 3 */
    for( std::vector<std ::string>::cons t_iterator iter = svec.begin();
    iter != svec.end(); ++iter)
    {
    std::string a_word;
    /* bind that line, to the istringstream */
    std::istringstr eam vector_line( *iter );
    >
    while( vector_line >a_word )
    {
    std::cout << a_word << " ";
    }
    }
    >
    std::cout << std::endl;
    >
    return 0;
    }
    >
    --------- OUTPUT ------------
    [arnuld@Arch64 c++]$ g++ -ansi -pedantic -Wall -Wextra ex_08-16.cpp
    [arnuld@Arch64 c++]$ ./a.out
    Enter full path of the file: /home/arnuld/programming/scratch.txt
    ;; This buffer is for notes you don't want to save, and for Lisp
    evaluation. ;; If you want to create a file, visit that file with C-x
    C-f, ;; then enter the text in that file's own buffer.
    [arnuld@Arch64 c++]$

    V
    --
    Please remove capital 'A's when replying by e-mail
    I do not respond to top-posted replies, please don't ask


    Comment

    • pavel.turbin@gmail.com

      #3
      Re: using sstream to read a vector&lt;strin g&gt;

      You should probably have wstring support as well. Depends on command
      line parameter you read stream into
      vector<stringor vector<wstring>




      Comment

      • Jerry Coffin

        #4
        Re: using sstream to read a vector&lt;strin g&gt;

        In article <1189842321.771 650.274860@50g2 000hsm.googlegr oups.com>,
        geek.arnuld@gma il.com says...

        [ ... ]
        /* step 3 */
        for( std::vector<std ::string>::cons t_iterator iter = svec.begin();
        iter != svec.end(); ++iter)
        {
        std::string a_word;
        /* bind that line, to the istringstream */
        std::istringstr eam vector_line( *iter );
        >
        while( vector_line >a_word )
        {
        std::cout << a_word << " ";
        }
        }
        I'd probably code this section something like this:

        std::copy(std:: istream_iterato r<std::string>( svec.begin(), svec.end(),
        std::ostream_it erator<std::str ing>(std::cout, " "));

        I know it was required in an earlier exercise, but I can't say I'm
        terribly enthused about the read_file function either. For reading data
        a line at a time, I prefer a proxy to use with std::copy. Using that,
        your program can be written something like this:

        #include <string>
        #include <vector>
        #include <iterator>
        #include <algorithm>
        #include <sstream>
        #include <iostream>
        #include <fstream>

        // string proxy that reads a line at a time
        class line {
        std::string data;
        public:
        std::istream &read(std::istr eam &is) {
        return std::getline(is , data);
        }

        operator std::string() const { return data; }
        };

        std::istream &operator>>(std ::istream &is, line &d) {
        return d.read(is);
        }

        // Write the words in a string out as a series of space-separated words.
        class write_words {
        std::ostream &os_;
        public:
        write_words(std ::ostream &os) : os_(os) {}

        void operator()(std: :string const &line) {
        std::stringstre am words(line);

        std::copy(std:: istream_iterato r<std::string>( words),
        std::istream_it erator<std::str ing>(),
        std::ostream_it erator<std::str ing>(os_, " "));
        }
        };

        // To keep things simple, I'm assuming the filename is passed on the
        // command line
        int main(int argc, char **argv) {
        std::vector<std ::stringsvec;

        // The following block is vaguely equivalent to read_file
        {
        std::ifstream in(argv[1]);

        std::copy(std:: istream_iterato r<line>(in),
        std::istream_it erator<line>(),
        std::back_inser ter(svec));
        }

        std::for_each(s vec.begin(), svec.end(), write_words(std ::cout));
        return 0;
        }

        Personally, I find this quite a bit more readable. Instead of the
        "steps" being encoded as comments, the code itself expresses each step
        fairly directly. The one fact that may not be immediately obvious is
        that since 'in' is a local variable, it is automagically closed upon
        leaving the block in which it was created. I _strongly_ prefer this to
        explicitly calling in.close() -- it's much easier to produce exception-
        safe code this way. That may not be much of an issue for you yet, but
        you're progressing quickly enough that I'd guess it will be soon, and
        it's a lot easier if you don't have to re-learn everything when you do.

        As an aside, I suppose I should add that for a real program, more error-
        checking is clearly needed. You should clearly check that argc>1 before
        trying to use argv[1] -- though as Victor (I think it was Victor anyway)
        suggested, you might want to use the file specified on the command line
        if there was one, and prompt for an input file name otherwise. If you
        decide to do that, I'd move the code to do so into a function instead of
        leaving it directly in main though, something like:

        char *file_name = argv[1];
        if (file_name == NULL) // argv[argc] == NULL
        file_name = get_file_name(s td::cin);

        --
        Later,
        Jerry.

        The universe is a figment of its own imagination.

        Comment

        • Jerry Coffin

          #5
          Re: using sstream to read a vector&lt;strin g&gt;

          In article <1189842321.771 650.274860@50g2 000hsm.googlegr oups.com>,
          geek.arnuld@gma il.com says...

          [ ... ]

          I've done a bit more playing around with the code I posted earlier. I
          wasn't entirely happy about using 'std::for_each' to do what was really
          a copy operation. Admittedly, by using 'write_words(st d::cout)' in the
          for_each, it was fairly apparent what was happening. Nonetheless, I
          prefer to copy things with std::copy. The proxy-based approach can be
          extended to deal with output just as well as input, so I decided to try
          that out:

          #include <string>
          #include <vector>
          #include <iterator>
          #include <algorithm>
          #include <sstream>
          #include <iostream>
          #include <fstream>

          class line {
          std::string data;
          static const std::string empty;
          public:
          std::istream &read(std::istr eam &is) {
          return std::getline(is , data);
          }

          line(std::strin g const &init) :data(init) {}
          line(){}

          std::ostream &write(std::ost ream &os) const {
          std::stringstre am x(data);
          std::copy(std:: istream_iterato r<std::string>( x),
          std::istream_it erator<std::str ing>(),
          std::ostream_it erator<std::str ing>(os, " "));
          return os;
          }

          operator std::string() const { return data; }
          };

          std::istream &operator>>(std ::istream &is, line &d) {
          return d.read(is);
          }

          std::ostream &operator<<(std ::ostream &os, line const &d) {
          return d.write(os);
          }

          int main(int argc, char **argv) {
          std::vector<std ::stringsvec;
          std::ifstream in(argv[1]);

          std::copy(std:: istream_iterato r<line>(in),
          std::istream_it erator<line>(),
          std::back_inser ter(svec));

          std::copy(svec. begin(),
          svec.end(),
          std::ostream_it erator<line>(st d::cout, " "));

          return 0;
          }

          Class line is now something of a misnomer. In reality, 'line' should
          probably be left as it was before, and a new class (word_writer or
          something like that) created for the output. I didn't bother for now,
          mostly because it makes the code longer, and I doubt it makes much
          difference in understanding what I've done. I suspect many people
          probably find the previous version more understandable than this anyway.

          --
          Later,
          Jerry.

          The universe is a figment of its own imagination.

          Comment

          • arnuld

            #6
            Re: using sstream to read a vector&lt;strin g&gt;

            I really don't understand what exactly is going on here :(

            Comment

            • Jerry Coffin

              #7
              Re: using sstream to read a vector&lt;strin g&gt;

              In article <1189917049.239 406.142370@50g2 000hsm.googlegr oups.com>,
              geek.arnuld@gma il.com says...
              I really don't understand what exactly is going on here :(
              Sorry 'bout that. I undoubtedly should have included quite a bit more
              commentary about the code when I posted it.

              As I mentioned previously, our first class is a proxy. It's mostly a
              stand-in for std::string, but it changes a few specific things about how
              a string acts.

              The first thing we change is how we read our special string type.
              Instead of reading a word at a time, we read an entire line at a time:

              std::istream &read(std::istr eam &is) {
              return std::getline(is , data);
              }

              That's all this does: make it so line.read(some_ istream) reads a whole
              line of data into a string.

              line(std::strin g const &init) :data(init) {}

              This just lets us initialize a line from a string. It just copies the
              string into the line's data member (which is a string, so it doesn't
              really change anything).

              line(){}

              This default ctor would have been there by default, but since we added
              the ctor that creates a line from a string, we have to explicitly add it
              back in or it won't be there -- and we generally need a default ctor for
              anything we're going to put into any standard container.

              std::ostream &write(std::ost ream &os) const {

              std::stringstre am x(data);

              This much takes the line that we read in, and puts it into a
              stringstream.


              std::copy(std:: istream_iterato r<std::string>( x),
              std::istream_it erator<std::str ing>(),
              std::ostream_it erator<std::str ing>(os, " "));

              Now we take our stringstream, read out one word at a time, and write it
              out to a stream. It's roughly equivalent to code like:

              std::string temp;
              while ( x >temp) {
              os << temp;
              os << " ";
              }

              The big difference is that std::copy can take essentially any pait of
              iterators as input, and write those items to essentially any iterator
              for the output.

              operator std::string() const { return data; }

              This is a crucial part of allowing our 'line' class to act like a
              string: we allow it to be converted to a string at any time. This
              becomes crucial in how we use it below.

              std::istream &operator>>(std ::istream &is, line &d) {
              return d.read(is);
              }

              std::ostream &operator<<(std ::ostream &os, line const &d) {
              return d.write(os);
              }

              These two functions just overload operator>and << to use our member
              funtions. They allow code to read a line with code like:

              std::cin >some_line;

              and it reads an entire line of data instead of just one word.



              int main(int argc, char **argv) {
              std::vector<std ::stringsvec;

              std::ifstream in(argv[1]);

              This just takes the first name that was passed on the command line and
              opens it as a file.

              std::copy(std:: istream_iterato r<line>(in),
              std::istream_it erator<line>(),
              std::back_inser ter(svec));

              Now, here's were we get a little bit tricky: even though we have a
              vector of string, we use an istream_iterato r<line>. That means the
              istream_iterato r uses the operator>we defined above (which, in turn,
              calls line::read) to read in each item from the stream. It then uses
              push_back to add that item to svec. This, in turn, uses the operator
              string we defined above, so it ends up using the entire string that we
              read in (i.e. the entire line we read with readline in line.read().

              std::copy(svec. begin(), svec.end(),
              std::ostream_it erator<line>(st d::cout, " "));

              Here we reverse things: we copy the contents of svec out to an
              ostream_iterato r for std::cout -- but it's an ostream_iterato r<line>, so
              when it writes each complete line out to std::cout, it does so using
              line.write. That, in turn, creates a stringstream and writes out each
              word in the line individually instead of writing out the whole line.

              Our proxy (line) is pretty close to what most people initially think
              they will do with inheritance -- create a new class that acts like the
              existing class (string, in this case) except for changing a couple of
              specific operations (input and output, in this case). The ctor and cast
              operator we provided in line allow us to freely convert between this
              type and string whenever we want/need to. In this case, we read in
              lines, convert and store them as strings, then convert them back to
              lines as we write them out.

              Given that we never do anything _else_ with the strings, we could
              eliminate all the conversions by creating svec as a vector<lineinst ead
              of a vector<string>. This, however, would have a substantial
              disadvantage if we did much of anything else with our strings -- we'd
              have to explicitly provide access to any other string operations, and as
              we all know, there are a LOT of string operations. Providing the
              conversions allows us to support all the string operations with very
              little work.

              --
              Later,
              Jerry.

              The universe is a figment of its own imagination.

              Comment

              Working...