PPP2 Chapter 11 Exercise 2

Pages: 12
Exercise specifications:

Write a program that given a file name and a word outputs each line that
contains that word together with the line number. Hint: getline().


My code right Now:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// chap11ex2.cpp : Defines the entry point for the console application.
// Osman Zakir
// 2 / 28 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 11 Exercise 2
// Exercise Specifications:
/**
 * Write a program that given a file name and a word outputs each line that
 * contains that word together with the line number. Hint: getline().
 */

#include "../../cust_std_lib_facilities.h"
#include <iostream>
#include <fstream>
#include <string>
#include <vector>

std::string input_word();

int main()
{
	using namespace std;
	cout << "Please enter input file name\n";
	string iname;
	cin >> iname;
	ifstream ifs{ iname };
	try
	{
		if (!ifs)
		{
			error("can't open input file ", iname);
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "runtime error: " << e.what() << '\n';
	}

	cout << "Please enter output file name\n";
	string oname;
	cin >> oname;
	ofstream ofs{ oname };
	try
	{
		if (!ofs)
		{
			error("can't open output file ", oname);
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "runtime error: " << e.what() << '\n';
	}

	string word = input_word();
	vector<string> words;
	while (getline(ifs, word))
	{
		words.push_back(word);
	}
}

std::string input_word()
{
	using namespace std;
	string word;
	cin >> word;
	return word;
}


I'm having trouble figuring out how to read a line with getline() and then match the word with the input word. Do I make another string variable to hold the string being read into and match that with the input word?

And this is the file I have (let me know if it's good enough for this program):

dragon danger flame fire blaze wyvern lizard lizards blazing flaming dangers dangerous dragons
fly float glide gliding floating flying floats flies glider gliders
two twin twins triplets triple triples doubles


Since getline() also seems to require a delimiter, I'll just pass in a space character.
To check if a word is in the line you read with getline you need to use the string::find method. Also you need a counter for the line number. However the vector is not needed. Just output the line to the output file if it contains the search word.
Yeah, I'll do that, then.

But wait a minute. I'm using an output file, but the exercise doesn't say anything about using an output file. I'll take the output file out and just send the program's output to the screen.

Edit: Would this be fine?
1
2
3
4
5
6
7
8
9
10
11
12
string word = input_word();
	while (!ifs.eof())
	{
		string line;
		int line_num = 1;
		getline(ifs, line, ' ');
		size_t found = line.find(word);
		if (!found)
		{
			line_num++;
		}
	}


If so, how do I get the correct line number output the words in that line?
Last edited on
You'd have to increment line_num irrespective of whether or not word is found within line, also if you don't use the vector method you'd have to check for word boundaries or you could get false positives as in the example below:
1
2
3
4
5
6
7
8
9
#include <iostream>
#include <string>

int main()
{
    std::string line = "Ripoff marketing is bad for consumers";
    std::string key = "of";
    std::cout << line.find(key);
}

edit: mention case-sensitivity, punctuation as well
Last edited on
And this is the file I have (let me know if it's good enough for this program):

You probably should have some words that repeat in some of the different lines.

Since getline() also seems to require a delimiter, I'll just pass in a space character.

No getline() doesn't require a delimiter. Did you find and read any documentation for this function? The third parameter to this function is optional (this parameter has a default value of '\n' (the new line character)). Since you want a full line are you sure you want to use a space as the third parameter.

http://www.cplusplus.com/reference/string/string/getline/

The third argument is optional, but it says "delim" on the prototypes for the versions that need three arguments.

It's a newline as the default third argument, right? So if you provide something other than a newline, you can get a whole line. But the versions of the function that take only two arguments should read a whole line, too.

@Gunnerfunner: If I use a space as a delimiter, won't it look for spaces to know where a word ends? Or am I looking at it wrong?
If I use a space as a delimiter, won't it look for spaces to know where a word ends? Or am I looking at it wrong?


I suppose you're thinking " of " instead of plain "of" but what about "of,", "of.", "Of", "of!" etc?
It's a newline as the default third argument, right?

Correct.

So if you provide something other than a newline, you can get a whole line.

No, you get everything up to the delimiter, so if you use a space character as the delimiter you will get all of the characters until a space character is encountered, not necessarily a line, more likely a single word.

But the versions of the function that take only two arguments should read a whole line, too.

Yes the versions of getline() that take two parameters will retrieve a complete line because it uses the new line character as a delimiter.

EDIT:
By the way the two argument getline(stream&, string&) just calls the three argument version supplying the new line character as the delimiter.

If I use a space as a delimiter, won't it look for spaces to know where a word ends?

But be careful if you use the space character as a delimiter getline() will only use a single space as the delimiter, it will not detect any of the other whitespace delimiters, such as the tab character or the new line character. If you want a single word you would be better off using the extraction operator>> instead of getline().

Last edited on
Okay, I'll use getline() with two arguments, then. But how should I count the number of lines (and is the code I already have for that good enough?)? That and how do I get all of the words on the line so I can print them out?
One way:
1
2
3
4
5
6
7
8
  std::string line;
  size_t lineCounter = 1;

  while (getline(ifs, line))
  {
      std::cout << lineCounter << ": " << line << "\n";
      lineCounter++;
  }
If I put that inside a condition checking if the current line contains the input word (I'd put the update to the counter variable after the condition), would it work? Seems like it might.
Last edited on
Yes it should. On line 6 just replace the output with your condition check.
I tried it and it almost works, but I guess I did something wrong because inputting "fly" doesn't make it output both lines 2 and 4 like it should.

Here's the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// chap11ex2.cpp : Defines the entry point for the console application.
// Osman Zakir
// 2 / 28 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 11 Exercise 2
// Exercise Specifications:
/**
 * Write a program that given a file name and a word outputs each line that
 * contains that word together with the line number. Hint: getline().
 */

#include "../../cust_std_lib_facilities.h"
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <string>

std::string input_word();
void print_line(std::istream &is);

int main()
{
	using namespace std;
	cout << "Please enter input file name\n";
	string iname;
	cin >> iname;
	ifstream ifs{ iname };
	try
	{
		if (!ifs)
		{
			error("can't open input file ", iname);
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "runtime error: " << e.what() << '\n';
	}

	print_line(ifs);
	keep_window_open();
}

std::string input_word()
{
	using namespace std;
	string word;
	cin >> word;
	return word;
}

void print_line(std::istream &is)
{
	using namespace std;
	string line;
	cout << "Input word to search for: ";
	string word = input_word();
	size_t line_counter = 1;
	size_t i = 0;
	while (getline(is, line))
	{
		if (word[i] == line[i])
		{
			cout << line_counter << ": " << line << '\n';
		}
		line_counter++;
		i++;
	}
}


Here's the most recent output:

Please enter input file name
words_input.txt
Input word to search for: fly
2: fly float glide gliding floating flying floats flies glider gliders
Please enter a character to exit
k
Press any key to continue . . .


And here's the input file:

dragon danger flame fire blaze wyvern lizard lizards blazing flaming dangers dangerous dragons
fly float glide gliding floating flying floats flies glider gliders
two twin twins triplets triple triples doubles
dragon twin triplets doubles triple fly flier flying
The problem is that you only compare the first char of word with the first char of line.
Then the while loop goes to the next line...
Line 2 starts with f and so it's printed but the other lines are not.
I would move the check for word to a seperate function like:
bool contains(const string& haystack, const string& needle
In this way you can test it separately.
Or I could use string::find()? What string should call it with (like if I call it as str1.find(str2);, which string should str1 be?)? Is it like this? line.find(word);
line.find would work. I am not sure how exact it needs to be.
Does it have to be full word only?
Does it have to be case-sensitive?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <string>
#include <cctype>
#include <sstream>
#include <fstream>
#include <iomanip>

std::string to_lower( std::string str )
{
    for( char& c : str ) c = std::tolower(c) ;
    return str ;
}

std::string replace_punct( std::string str ) // replace punctuation with space
{
    std::string result ;
    for( char c : str ) result += std::ispunct(c) ? ' ' : c ;
    return result ;
}

std::string trim( std::string str ) // trim leading/trailing whitespace
{
    while( !str.empty() && std::isspace( str.back() ) ) str.pop_back() ;

    std::size_t pos = 0 ;
    // while( pos < !str.size() && std::isspace( str[pos] ) ) ++pos ;
    while( pos < str.size() && std::isspace( str[pos] ) ) ++pos ;
    return str.substr(pos) ;
}

bool contains_word( std::string line, std::string word )
{
    word = trim( replace_punct( to_lower(word) ) ) ;

    std::istringstream stm( replace_punct( to_lower(line) ) ) ;

    std::string token ;
    while( stm >> token ) if( token == word ) return true ;

    return false ;
}

int main()
{
    std::ifstream file( __FILE__ ) ;
    std::string word = "While" ;

    std::streamoff line_number = 0 ;
    std::string line ;
    while( std::getline( file, line ) )
    {
        ++line_number ;
        if( contains_word( line, word ) )
        {
            std::cout << std::setw(5) << std::setfill('0') << line_number
                      << ". " << line << '\n' ;
        }
    }
}

http://coliru.stacked-crooked.com/a/4a08f543ad921405
Last edited on
Thanks. It worked for me, though I make some changes. I used string::find() in contains_word() to see if the word was there, and I didn't use trim() because I don't know how to fix the warning message I get when I use it (something about an unsafe use of bool).

Here's the code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// chap11ex2.cpp : Defines the entry point for the console application.
// Osman Zakir
// 2 / 28 / 2017
// Bjarne Stroustrup: Programming: Principles and Practice Using C++ 2nd Edition
// Chapter 11 Exercise 2
// Exercise Specifications:
/**
 * Write a program that given a file name and a word outputs each line that
 * contains that word together with the line number. Hint: getline().
 */

#include "../../cust_std_lib_facilities.h"
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <cctype>
#include <sstream>
#include <string>

std::string input_word();
void print_line(std::istream &is);
bool contains_word(std::string &line, std::string &word);
std::string replace_punct(std::string &str);
std::string to_lower(std::string &str);

int main()
{
	using namespace std;
	cout << "Please enter input file name\n";
	string iname;
	cin >> iname;
	ifstream ifs{ iname };
	try
	{
		if (!ifs)
		{
			error("can't open input file ", iname);
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "runtime error: " << e.what() << '\n';
	}

	print_line(ifs);
	keep_window_open();
}

std::string input_word()
{
	using namespace std;
	string word;
	cin >> word;
	return word;
}

void print_line(std::istream &is)
{
	using namespace std;
	cout << "Input word to search for: ";
	string line;
	string word = input_word();
	size_t line_counter = 1;
	while (getline(is, line))
	{
		if (contains_word(line, word))
		{
			cout << line_counter << ": " << line << '\n';
		}
		++line_counter;
	}
}

bool contains_word(std::string &line, std::string &word)
{
	using namespace std;
	word = replace_punct(to_lower(word));
	size_t found = line.find(word);
	if (found != string::npos)
	{
		return true;
	}
	return false;
}

std::string replace_punct(std::string &str)
{
	using namespace std;
	string result;
	for (char c : str)
	{
		result += ispunct(c) ? ' ' : c;
	}
	return result;
}

std::string to_lower(std::string &str)
{
	for (char &c : str)
	{
		c = tolower(c);
	}
	return str;
}
and I didn't use trim() because I don't know how to fix the warning message I get when I use it (something about an unsafe use of bool).

Then post the complete error message along with the code that actually generated the warnings and perhaps someone can point you in the right direction. A big part of programming is learning to understand what your compiler is telling you.

EDIT:

By the way the following is a horrible use of exceptions, IMO. Since you're in main() why not just print the error message and return to the operating system?

1
2
3
4
5
6
7
8
9
10
11
try
	{
		if (!ifs)
		{
			error("can't open input file ", iname);
		}
	}
	catch (const runtime_error &e)
	{
		cerr << "runtime error: " << e.what() << '\n';
	}
Last edited on
In VS 2015 CE the warnings are:

warning C4804: '<': unsafe use of type 'bool' in operation
warning C4018: '<': signed/unsigned mismatch

They refer to while (pos < !str.size() && std::isspace(str[pos]))

I guess it's just a typo, the ! doesn't make much sense there.
Pages: 12