/* Lecture code 7.0 * * The CityFinder example. For this code to work correctly, you'll need to place it in the * same directory as the place-data.txt file bundled as lecture code 7.1. Feel free to play * around with this code - if you come up with any interesting examples, I'd love to see them. * * Comments have been added to the relevant sections. */ #include #include #include #include #include #include #include #include // for set_intersection #include // for back_inserter. using namespace std; /* Type: cityT * A type that stores a city's longitude, latitude, and name. */ struct cityT { double latitude; double longitude; string name; }; /* The number of degrees of longitude per mile, when. * measured at the equator. This number is mostly * accurate for the US, though for polar regions in * Alaska might be a bit skewed. */ const double DegreesPerMile = 1.0 / 69.172; /* A useful typedef so that we don't have to explicitly * spell out the full type of the coordinate lookup maps. * typedef works like const but on types and can be used to * create aliases for existing data types. */ typedef multimap CoordLookup; /* Straight out of the earlier lecture code. */ string GetLine() { string result; getline(cin, result); return result; } /* A modified version of the GetInteger function * that accepts positive real numbers. */ double GetPositiveReal() { while(true) { stringstream converter; converter << GetLine(); double result; converter >> result; if(converter.fail()) cout << "Please enter a real number." << endl; else { char ch; converter >> ch; if(converter.fail()) { if(result > 0.0) return result; else cout << "Please enter a positive real number." << endl; } else cout << "Unexpected character: " << ch << endl; } cout << "Retry: "; } } /* Loads the place-data.txt file from disk and populates a * map with the information. */ void LoadData(map &cityLookup) { ifstream input("place-data.txt"); string data, name; /* This uses a bit of shorthand that reads in the line and then * continues looping while the input stream isn't in a fail state. * In general, you can test if(myStream) as shorthand for * if(!myStream.fail()); */ while(getline(input, name)) { /* Assuming data is well-formatted, read in the line * with the longitude and latitude data and run it * through a stringstream. */ getline(input, data); stringstream converter(data); /* Load the information into the cityT struct. * We'll assume the file is well-formed. */ cityT city; city.name = name; converter >> city.latitude; converter >> city.longitude; /* Insert the key-value pair "name: city" into the map. */ cityLookup.insert(make_pair(name, city)); } /* Print some useful information out. */ cout << "Data exists for "; cout << cityLookup.size() << " cities." << endl; } /* Given a map from city names to cityT structs, populates the * longLookup and latLookup tables to be lookups from longitude to * city and latitude to city. */ void PopulateLookupTables(map& cityLookup, CoordLookup& longLookup, CoordLookup& latLookup) { for(map::iterator itr = cityLookup.begin(); itr != cityLookup.end(); ++itr) { /* Recall that itr->second is the value part of a key/value pair. * This means that itr->second is the cityT associated with the current * key. */ longLookup.insert(make_pair(itr->second.longitude, &itr->second)); latLookup.insert(make_pair(itr->second.latitude, &itr->second)); } } /* Actually runs the algorithm. Remember that we'll be computing slices of * longitude and latitude that happen to be near the target city, then intersecting * them. */ void FindNearbyCities(cityT& center, double radius, CoordLookup& longLookup, CoordLookup& latLookup) { /* Compute two sets - one of cities with nearby longitude and one with * nearby latitude. */ set nearbyLongs, nearbyLats; /* Use lower_bound and upper_bound to only iterate over the slices we want to * consider. Note that in professional code you would probably compute upper_bound * just once and store it outside the for loop rather than recomputing it multiple * times. */ for(CoordLookup::iterator itr = longLookup.lower_bound(center.longitude - radius); itr != longLookup.upper_bound(center.longitude + radius); ++itr) nearbyLongs.insert(itr->second); for(CoordLookup::iterator itr = latLookup.lower_bound(center.latitude - radius); itr != latLookup.upper_bound(center.latitude + radius); ++itr) nearbyLats.insert(itr->second); /* Use the set_intersection algorithm to compute the intersection of the two * city ranges. This uses iterator adapters and algorithms, which we haven't * covered yet, so be sure to consult the STL Iterators 2 and STL Algorithms * handouts once they're available for more information. */ vector intersection; set_intersection(nearbyLongs.begin(), nearbyLongs.end(), nearbyLats.begin(), nearbyLats.end(), back_inserter(intersection)); /* Manually iterate over the vector and check to see that each point is indeed * within the bounds we want. */ for(int i = 0; i < intersection.size(); ++i) { double dx = intersection[i]->longitude - center.longitude; double dy = intersection[i]->latitude - center.latitude; if(dx * dx + dy * dy <= radius * radius) cout << intersection[i]->name << endl; } } int main() { map cityLookup; CoordLookup longLookup; CoordLookup latLookup; /* Fill our tables with some info. */ LoadData(cityLookup); PopulateLookupTables(cityLookup, longLookup, latLookup); while(true) { /* Prompt the user for a city name. */ cout << "Please enter a city: "; string city = GetLine(); /* If the user just hit enter, we're done. */ if(city == "") break; /* Look up the city, print data if found. */ map::iterator itr = cityLookup.find(city); if(itr != cityLookup.end()) { cout << "Enter radius: "; double radius = GetPositiveReal() * DegreesPerMile; FindNearbyCities(itr->second, radius, longLookup, latLookup); } else cout << "City not found." << endl; } return 0; }