Using cin, getline(), and maps.. Unexpected Behavior

I'm trying to solve this problem:
https://open.kattis.com/problems/baconeggsandspam

I've commented out some of my code as it's unrelated to the issue I'm posting about. I should note that this was a homework assignment, however, it was due like 2 weeks ago and the teacher doesn't accept late work. I'm doing this so I can better understand important concepts.

The first issue I'm having is when I ask the user to input the number of lines they're about to input (in n variable), it will ask the user for a different number of lines than they input. For example, if I input 2 then it should ask for 2 following lines of input (using getline). However, it actually asks them for 3. I suspect this has to do with cin.ignore(1000, '\n'); but I've put that in and it's still not working.

The second issue I'm having is that my code checks whether each word starts with a capital letter. If it does, it should store it into the map key because it's a person's name. Else it should store the word into the map value (in a vector). This appears to work fine for the first name. But it appears to skip over the first letter in the next names.

Example Input:
Notice that all names start with an uppercase letter and all items start with a lowercase letter.
1
2
3
4
[Debug] Please input the number of lines following: 2
Jessie bacon eggs apples
Tom carrots apples
Hamlet eggs bacon waffle


Example Output:
1
2
3
4
5
6
7
8
9
[Debug] splitStrings() function called!
[Debug] word[0] = J
[Debug] word[0] is a name: Jessie
[Debug] word[0] = b
[Debug] word[0] = e
[Debug] word[0] = a
[Debug] word[0] = o
[Debug] word[0] = c
[Debug] word[0] = a


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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <sstream> // split string
#include <fstream> // split string
#include <algorithm> // sort()

// Will only be using for stuff I'm new to. Will make it easier
// to learn.
using namespace std;

//operator overloaded to print a map container:
template<class T1, class T2>
ostream& operator<<(ostream &out, const map<T1, T2> &map) {
	out << '{';
	for (auto &element : map) {
		out << ' ' << element.first << ':' << element.second;
	}
	out << '}';
	return out;
}

// operator overloaded to print a vector:
template<class T>
ostream& operator<<(ostream &out, const vector<T> &v) {
	out << '[';
	for (auto &element : v) {
		out << ' ' << element;
	}
	out << "]\n";
	return out;
}

// Key = name of person
// Values = menu items for each person
std::map<std::string, std::vector<std::string> > nameToItemMap = {

};

// referencing functions
void splitStrings(std::vector<std::string> &allStrings);



int main() {
    int n = 0;
	std::string userInLine = ""; // will contain name and items person ordered
    std::vector<std::string> allStrings; // contains all names and items ordered

    std::cout << "[Debug] Please input the number of lines following: ";
    std::cin >> n;
	std::cin.ignore(1000, '\n'); // preps for getline

    // for the number of lines that are going to be input:
    for (int i = 0; i < n; i++) {
		// Gets full line of input as one string then assigns
		// it to allStrings vector.
		std::getline(std::cin, userInLine);
		std::cin.get(); // waits for input rather then going straight to running splitStrings()
		//std::cout << "[Debug] userInLine = " << userInLine << '\n';
		
		//std::cin.ignore(1000, '\n'); // preps for getline
		allStrings.push_back(userInLine);
    }

	// Do lines that will split string of input. Prob just call a function and do the main code elsewhere.
    splitStrings(allStrings);

	
	// Will go through the entire vector and print each part.
	/*
	std::cout << "[Debug] ";
	for (int i = 0; i < allStrings.size()-1; i++) {
		std::cout << allStrings[i] << ' ';
	}
	*/
	
    return 0;
}


//WIP
// will split strings, store them in map, and sort map vector values.
void splitStrings(vector<string> &allStrings) {
	std::cout << "[Debug] splitStrings() function called! \n";

	std::string temp;
	std::string tempName;
	// for each line in the vector, split it into separate words
	// and store them into map / vector value.
	for (int i = 0; i < allStrings.size(); i++) {
		temp = allStrings[i];

		// assigns a string to in (stream)
		stringstream in(temp);

		for (std::string word; in >> word; ) {
			// if the beginning of the word is an uppercase letter:
			std::cout << "[Debug] word[0] = " << word[0] << '\n';
			if (word[0] >= 65 && word[0] <= 90) {
				// sets name that has been used most recently to
				// tempName. Will be used in else statement below.
				// Also, this if statement will ALWAYS take place
				// before the else statement so I don't have to worry
				// about tempName not having anything assigned.
				std::cout << "[Debug] word[0] is a name: " << word << '\n';
				// prep for the else statement below:
				tempName = word;
				// store word into map KEY at correct location:
				nameToItemMap[tempName];

			}

			// if the beginning of the word is a lowercase letter:
			else {
				// store the word into the map vector VALUE,
				// based on the name of the most recent person that
				// was stored as a map KEY.
				nameToItemMap[tempName].push_back(word);	
			}
		}
	}
	// Sort map & print sorted map:
	// for each value in the map-
    /*
	for (auto val : nameToItemMap) {
		std::cout << val.first;
		sort(val.second.begin(), val.second.end());
		// for each value in the vector of this particular key-
		for (auto n : val.second)
			std::cout << " " << n;
		std::cout << "\n\n";
	}
    */
	

	// prints map key and value.
	// uses operator overloaded functions at top.
	// Work in progress(?)
	//std::cout << nameToItemMap << '\n';
}


Help would be greatly appreciated. Thank you!
Last edited on
Line 60: Why are you cin.get()ing anything here? Don’t do that.

Your code seems to recognize capitalized words for me.
C:\Users\Michael\Programming\cpp\foo> a
[Debug] Please input the number of lines following: 3
1> One two three
2> four Five six
3> seven eight Nine
[Debug] splitStrings() function called!
[Debug] word[0] = O
[Debug] word[0] is a name: One
[Debug] word[0] = t
[Debug] word[0] = t
[Debug] word[0] = f
[Debug] word[0] = F
[Debug] word[0] is a name: Five
[Debug] word[0] = s
[Debug] word[0] = s
[Debug] word[0] = e
[Debug] word[0] = N
[Debug] word[0] is a name: Nine

C:\Users\Michael\Programming\cpp\foo> _

Hope this helps.
@Duthomhas
Thank you for your reply. I was using cin.get() because when I didn't use cin.get() it said the message:
[Debug] splitStrings() function called!

Right after I input the number of lines I was going to input (so before I even input any strings, if I remember correctly). I figured it would be calling the function with empty data so I forced it to wait for me to input text before calling the function splitStrings().

Anyways, by removing cin.get(); it fixed the problem. I don't completely understand why, but thank you!

Last edited on
The trouble with input will always be the same. Not just for this program, but for every program you ever write in the future... So, it is worth understanding exactly what is going on.


Whenever your user presses keys on the keyboard it goes into the keyboard input buffer. Even though it isn’t stored the same way behind the scenes, you can think of it just like a text file.

So if I press:

    Shift+H   E   L   L   O   Space   W   O   R   L   D   Shift+1   Enter

then that goes into an input stream, the same as the file:

1
2
Hello world!

The actual stream contains the characters:

      ┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬
  ... │ H │ e │ l │ l │ o │   │ w │ o │ r │ l │ d │ ! │\n │ ...
      ┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴
        ↑
        next character to read

Notice how we are looking at the stream: no beginning and no end.

When you ask for a space-delimited word (with std::cin >> a_string), the stream will skip any leading whitespace and then read all characters up until it finds a whitespace character.

      ┬───┬───┬───┬───┬───┬───┬───┬───┬
  ... │   │ w │ o │ r │ l │ d │ ! │\n │ ...
      ┴───┴───┴───┴───┴───┴───┴───┴───┴
        ↑
        next character to read

Extracted from stream and stored in your string: "Hello"

Notice how the next unread character is a space. If you again ask for a space-delimited string, C++ will skip the leading whitespace and again read everything until you get to whitespace:

      ┬───┬
  ... │\n │ ...
      ┴───┴
        ↑
        next character to read

Extracted from stream and stored in your string: "world!"

Notice how that newline (from when the user pressed Enter) is still unread? This is typical of all formatted input, stuff that looks like std::cin >> something.

However, if we use an unformatted input it reads everything, up to and including the newline:

      ┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬
  ... │ H │ e │ l │ l │ o │   │ w │ o │ r │ l │ d │ ! │\n │ ...
      ┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴
        ↑
        next character to read

then std::getline( std::cin, a_string ), we get:

      ┬
  ... │ ...
      ┴
        ↑
        next character to read

Everything is extracted, including the newline, and everything except the newline is stored in the string: "Hello world!".


This is why, after reading a number or anything using a formatted-input, you must then get rid of everything up to the next newline.

For example, if you ask for a number, and the input stream contains:

      ┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬
  ... │ 4 │ 2 │ o │ z │\n │ 1 │ 9 │ y │ r │ s │ ...
      ┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴
        ↑
        next character to read

after std::cin >> an_integer, the stream will contain:

      ┬───┬───┬───┬───┬───┬───┬───┬───┬
  ... │ o │ z │\n │ 1 │ 9 │ y │ r │ s │ ...
      ┴───┴───┴───┴───┴───┴───┴───┴───┴
        ↑
        next character to read

This is extra garbage left in the stream that needs to be removed. That is the reason for std::cin.ignore( std::numeric_limits <std::streamsize> ::max(), '\n' );, which simply reads and discards everything up to and including the next newline:

      ┬───┬───┬───┬───┬───┬
  ... │ 1 │ 9 │ y │ r │ s │ ...
      ┴───┴───┴───┴───┴───┴
        ↑
        next character to read

Now the input stream is (again) in the proper state to read the next input.

Keeping track of this is part of your job as the programmer. It helps to keep in mind one simple principle:

    The user will always press Enter after every input.

If you ask for a number, the user will type the number and press Enter.
If you ask for two numbers, the user will type two numbers and press Enter.
Whatever you ask for, the user will type it and press Enter.

A similar principle applies for text files. C++ handles everything with the same interface.

Hope this helps.
Last edited on
@Duthomhas
Thank you for the very detailed reply explaining it!

Here's what I'm confused about with your post:
For your part about inputting:
4 │ 2 │ o │ z │\n │ 1 │ 9 │ y │ r │ s

The user is actually inputting this right?
1
2
42oz
19yrs

(The user would press Enter after typing 42oz)

So I would think std::cin >> an_integer would store
"42oz" into an_integer variable, not "4" like your post says. Unless | represents a space, but in the Hello World! example that doesn't appear to be the case, so I don't think | is representing a space.

Again, thank you for the very detailed reply, I appreciate you trying to help me understand rather than just providing me with the solution to my code! :)
Good job for asking questions to clarify!

The user is actually inputting this right?
1
2
42oz
19yrs

(The user would press Enter after typing 42oz)

Exactly correct! ;o)

So I would think std::cin >> an_integer would store
"42oz" into an_integer variable, not "4" like your post says.

I actually never said what is put in the an_integer variable, but what goes in is 42. You cannot stick "oz" in an integer, and the formatted input will not even try.

That is why what is left in the stream after std::cin >> an_integer is "oz\n" + other stuff.

Asking for clarification is the hallmark of intelligence, so, thank you!
You’ve made my day. :O)
Ahh okay, thanks. I didn't realize that cin >> an_integer would ignore characters that aren't integers (like the oz from 42oz). I just assumed an_integer was actually a string (despite the name) otherwise it would give an error (based on my knowledge, which proved to be incorrect). :P

Haha, thanks. :) I appreciate thorough explanations and encouragement/positive responses (rather than responding in a way that makes me feel like an idiot). :P

Anyways, very much appreciated. You've answered all my questions.

Last night I saw the Angry Birds movie for the first time. I actually enjoyed it much more than I thought I would... So... I’m enjoying your username. LOL.
Lol, didn't even occur to me someone would/could make that connection, but now that I think about it that makes sense haha.

It's my gaming username and I ended up using it for programming too. Glad you like it. :P
Last edited on
Topic archived. No new replies allowed.