Week 6

String Streams

The <sstream> header contains classes which allow you to associate a stream with a string in memory, in the same way that the classes in <fstream> allow you to associate a stream with a file. Looking at the class hierarchy below, you can see that istringstream is a kind of istream, (just as ifstream is), while ostringstream is a kind of ostream, just like ofstream is. Stream class heirarchy with string streams highlighted.

To use a string stream for output, follow these three steps:

  1. Create an ostringstream object.
  2. Write to the stream object.
  3. Collect the results using the stream's str() member function.
ostringstream out;               // 1. Create the stream
out << "The answer is " << 42;   // 2. Write to the stream
string result = out.str();       // 3. Collect the results

As you can see, this is most useful when you want a formatted number as part of some other output.

Week 6

The format Function

Let's look at an example, where string strings can be very useful. The standard library has a to_string() function (starting in C++11) which works for all types of numbers. Unfortunately, for floating-point numbers, you have no control over the output format, which isn't very useful. Let's fix that.

Here's a short function with two arguments: a double for the value, and the number of decimal digits to display The function returns the value as a formatted string. (Note the default argument, which makes the function easier to use.)

string format(double value, int digits=2)
{
    ostringstream out;
    out << fixed << setprecision(digits) << value;
    return out.str();
}

You can use the format() function like this:

cout << format(2.456) << endl;      // 2.46
cout << format(1.0 / 3.0, 5) << endl;     // .33333
    
Week 6

Input String Streams

The C++11 string class introduced several new functions, like stoi() and stod(), which convert string values like 42 to int or double. Prior to C++11, you used the istringstream class.

istringstream in("January 3, 2020");

The istringstream (or input string stream) variable named in allows you to parse all of the pieces of the date that has been supplied, and, in a much easier manner than using the string functions like find() and substr():

string month; int day, year;
in >> month >> day; // read month and day
in.get();           // consume comma
in >> year;         // read and convert year

For complex values, like dates, the istringstream technique is the most efficient.

Week 6

An Input String Stream Exercise

Decorative running-man icon used for links

Using an input string stream is the easiest way to parse the individual parts of a line of text. Let's solve a problem which puts this to work. Click on the "running man" to open the starter code in CodeCheck.

Write a function inputStats which takes an input stream and an output stream as arguments. Report the number of lines in the file, the longest line, the number of tokens on each line, and the length of the longest token on each line. Assume at least one line of input and that each line has at least one token.

For example, if input contains the following text:

"Beware the Jabberwock, my son,
the jaws that bite, the claws that catch,
Beware the JubJub bird and shun
the frumious bandersnatch."

Then the output should be:

Line 1 has 5 tokens (longest = 11)
Line 2 has 8 tokens (longest = 6)
Line 3 has 6 tokens (longest = 6)
Line 4 has 3 tokens (longest = 14)
Longest line: the jaws that bite, the claws that catch,

When you tackle a complex problem like this, you should always tackle it one piece at a time. Let's start with this:

  1. Reading the entire input file, line-by-line
  2. Finding the longest line
  3. Printing the longest line to the output file
Week 6

The Arguments

Take a look at the starter code for the inputStats function. Reading the documentation comment, you can see that the two arguments it expects are an input stream and an output stream. The inputStats problem

The first question is "What kind of streams should you pass?" You should be able to call the function in any of these ways:
inputStats(cin, cout);
inputStats(inFile, outFile);  // ifstream, ofstream
inputStats(inStr, outStr);    // istringstream, ostringstream

To make sure it will work with all of these stream types, you must use the most general types for your parameters: istream and ostream.

In addition, all stream types must be passed by reference, not by value. If you forget this, your code will not compile, and the error message will not be helpful at all. Error message when passing streams by value.

Week 6

The Longest Line

Check Your Work
string line;
string longest_line;
while (getline(in, line))
{
    if (line.size() > longest_line.size())
   { 
        longest_line = line;
   } 
}
out << "Longest line: " << longest_line << endl;

To read line-by-line, use getline() and the data-loop pattern:

string line;
while (getline(in, line)) . . .

To find the longest line, you must:

  1. Create a variableto hold the longest line (before the loop)
  2. Compare the current line size to the longest line size (in the loop)
  3. Print the longest line (after the loop)

Here's the pseudocode for this part of the problem. Once you've turned it into C++, check your work with the code at the top of the page.

longest line : string
while getline line
    if line size > longest line size then
        longest line = line
print the longest line

Note: make sure that you read from your input stream, and write to your output stream. You should not use cin or cout in this problem; use your parameters.

Week 6

Counting the Lines

Check Your Work Here
string longest_line;
string line;
int line_number{0};
while (getline(in, line))
{
    ++line_number;
    if (line.size() > longest_line.size()) { 
        longest_line = line; 
   } 
    out << "Line " << line_number << " has ..." << endl;
}
out << "Longest line: " << longest_line << endl;

To process and count the lines, you must:

  1. Create a variable to hold the line number before the while loop.
  2. Increment the variable every time a line is read
  3. Print Line <line number> at the end of the while loop

Here's the pseudocode to do that with the new lines highlighted. Try it out and then check your work with the code I've supplied.

longest line : string
line number <- 0
while getline line
    line number = line number + 1
    if line size > longest line size then
        longest line = line
    Print "Line <line number> has . . ."
Print "Longest line: " longest line
Week 6

Processing the Tokens

Check Your Work Here
void inputStats(istream& in, ostream& out)
{
   string longest_line;
   string line;
   int line_number = 0;
   while (getline(in, line))
   {
       line_number++;
       if (line.size() > longest_line.size()) {
          longest_line = line;
     } 
       istringstream str_in(line);
       int num_tokens0{};
       string token;
       int longest_token = 0;
       while (str_in >> token)  {
          ++num_tokens; 
          if (token.size() > longest_token) {
             longest_token = token.size();
        } 
     } 
       out << "Line " << line_number << " has " 
           << num_tokens << " tokens (longest = " 
           << longest_token << ")" << endl;
  } 
   out << "Longest line: " << longest_line << endl;
}

To process and count the tokens, you must re-read each line, token-by-token. To do that you must:

  1. Create an input string stream variable using the line.
  2. Read (and count) each token from the string stream.

Here's the pseudocode that does this:

str_in : istringstream(line)
number of tokens <- 0
token : string
while str_in >> token
    number of tokens = number of tokens + 1
Print "Line <line number> has <number of tokens> tokens . . ."

The Longest Token

To find the longest token, follow the same pattern which you used to find the longest line.

longest token <- 0
while str_int >> token
    if token size > longest token then
        longest token = token size

Implement this code and your function should work correctly. If you have difficulty, then look at the code supplied above.