is there a simple way to do it with 2+ worded cities, like New York?
Answer: Yes.
As noted in the answer by @jackw11111, your problem in reading names containing whitespace was due to std::cin
that reads only from the beginning of the line up to the first whitespace. So for example attempting to read "New York"
with std::cin >> city;
would result in city
containing "New"
while "York"
remains in stdin
unread.
The solution is to use getline
, which will read up until the delimiter character specified (default: '\n'
).
Now, for the remainder of your problem, reading trip legs with two (or more) cities separated by '-'
, there are a number of ways you can approach the problem. Since each leg of a trip will include a from and to city, using std::pair provided as part of the STL utility
header would provide a convenient way to store related from and to cities. You can then use a std::vector<std::pair<..,..>>
to create a vector-of-pairs.
Unless you absolutely are required to read the first integer value for the number of trips taken -- you can do away with it as it isn't really needed to handle the input. In reality all you need is your home
city and then you can read as many lines of input as you like and store as many trip from and to city pairs until EOF
if reading from a file, or a blank line is entered to indicate end-of-input.
To separate lines read with getline
, using a std::stringstream. To read from the std::stringstream
, you can use getline
once again (with a delimiter of '-'
) to separate the from-to cities in the line. Placing the line in a stringstream and then reading from the stringstream prevents getline
from skipping over the '\n'
when the delimiter has been changed to something else.
So in your case you can declare the vector of pairs of strings along with a few additional strings to hold home
and help with parsing and you can simply read the home
city first, e.g.
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
int main (void) {
std::string home, last, trip, total; /* strings */
std::vector<std::pair<std::string, std::string>> vp {}; /* vector of pairs */
std::cout << "enter home city: "; /* prompt for home city */
if (!getline (std::cin, home)) { /* read/validate home city */
std::cerr << "error: failed to read home-city.\n";
return 1;
}
Now just loop reading lines of input and checking if the line is empty to indicate end-of-input. Create a stringstream from each line and then read from the stringstream with getline
(this eliminates the need of using substr
to parse cities from the line). Add each pair of from-to cities to your vector using std::make_pair:
/* prompt for trips (any number of legs per-line), ENTER alone when done */
std::cout << "\nenter trips (format city1-city2[-cityN]) "
"[Enter] alone when done\n\ntrip: ";
while (getline (std::cin, trip)) { /* while trip entered */
if (!trip.length()) /* if empty (zero length) break */
break;
std::stringstream ss (trip); /* create stringsteam from line */
std::string from, to; /* strings for from and to cities */
if (getline (ss, from, '-')) { /* get from city from stringstrem */
while (getline (ss, to, '-')) { /* get to city from stringstream */
vp.push_back(std::make_pair (from, to)); /* add pair to vector */
from = to; /* update from = to */
}
}
std::cout << "trip: "; /* prompt for next trip */
}
Finally, just loop over your stored pairs (with nested loops) to pick out all of the trip legs based on a from-to and then find the next by setting from = to;
and finding the next match. (you can also add additional checks to make sure that a proper from-to is found on each iteration -- that is left to you -- something simple like bool found;
will do)
total = home; /* initialize total & last to home */
last = home;
for (auto t1 : vp) { /* loop over each trip */
for (auto t2 : vp) { /* 2nd search loop */
if (t2.first == last) { /* find trip begins with last city */
total += "-" + t2.second; /* add trip end to total */
last = t2.second; /* update last to end */
break; /* go find next trip */
}
}
}
std::cout << '\n' << total << '\n'; /* output total trip beg to end */
(note: the first object in each std::pair
is accessed using .first
and the second using .second
-- even somewhat logical...)
There are many variation your can come up with. Putting it altogether, you could do:
#include <iostream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
int main (void) {
std::string home, last, trip, total; /* strings */
std::vector<std::pair<std::string, std::string>> vp {}; /* vector of pairs */
std::cout << "enter home city: "; /* prompt for home city */
if (!getline (std::cin, home)) { /* read/validate home city */
std::cerr << "error: failed to read home-city.\n";
return 1;
}
/* prompt for trips (any number of legs per-line), ENTER alone when done */
std::cout << "\nenter trips (format city1-city2[-cityN]) "
"[Enter] alone when done\n\ntrip: ";
while (getline (std::cin, trip)) { /* while trip entered */
if (!trip.length()) /* if empty (zero length) break */
break;
std::stringstream ss (trip); /* create stringsteam from line */
std::string from, to; /* strings for from and to cities */
if (getline (ss, from, '-')) { /* get from city from stringstrem */
while (getline (ss, to, '-')) { /* get to city from stringstream */
vp.push_back(std::make_pair (from, to)); /* add pair to vector */
from = to; /* update from = to */
}
}
std::cout << "trip: "; /* prompt for next trip */
}
total = home; /* initialize total & last to home */
last = home;
for (auto t1 : vp) { /* loop over each trip */
for (auto t2 : vp) { /* 2nd search loop */
if (t2.first == last) { /* find trip begins with last city */
total += "-" + t2.second; /* add trip end to total */
last = t2.second; /* update last to end */
break; /* go find next trip */
}
}
}
std::cout << '\n' << total << '\n'; /* output total trip beg to end */
}
Example Use/Output
$ ./bin/triptotal
enter home city: Paris
enter trips (format city1-city2[-cityN]) [Enter] alone when done
trip: Berlin-Paris
trip: Paris-New York
trip: New York-Zagreb
trip: Ljubljana-Berlin
trip: Zagreb-Ljubljana
trip:
Paris-New York-Zagreb-Ljubljana-Berlin-Paris
That could also be entered as:
$ ./bin/triptotal
enter home city: Paris
enter trips (format city1-city2[-cityN]) [Enter] alone when done
trip: San Antonio-New York-Berlin-Paris
trip: Paris-Frankfort-Zagreb
trip: Zagreb-Ljubljana-Dallas
trip: Dallas-San Antonio
trip:
Paris-Frankfort-Zagreb-Ljubljana-Dallas-San Antonio-New York-Berlin-Paris
The one additional task you will have is finding a way to handle multiple trip legs that may visit the same city. (that too is left for you) Look things over and let me know if you have further questions.