391 lines
15 KiB
C++
391 lines
15 KiB
C++
//An implement of CSCI-1200 HW2 Ride Sharing
|
|
//Author: Jinshan Zhou
|
|
//Date: 2025/1/20
|
|
//#include <algorithm>
|
|
//#include <cstdlib>
|
|
#include <cmath>
|
|
#include <vector>
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <iomanip>
|
|
|
|
#include "Driver.h"
|
|
#include "Rider.h"
|
|
|
|
void debug_print(const std::string &msg) {
|
|
std::cout << "DEBUG: " << msg << std::endl;
|
|
}
|
|
|
|
bool isPhoneNumberValid(const std::string &phone)
|
|
{
|
|
if (phone.size() != 12) return false;
|
|
//xxx-xxx-xxxx
|
|
for (int i = 0; i < 12; i++) {
|
|
if (i == 3 || i == 7) {
|
|
if (phone[i] != '-') return false;
|
|
} else {
|
|
if (!std::isdigit((unsigned char)phone[i])) return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void loadDrivers(std::ifstream &ifs, std::vector<Driver> &drivers) {
|
|
//read the file line by line, total of 13
|
|
while (!ifs.eof()) {
|
|
std::string fName, lName, gender, phone, vehicleType, state;
|
|
std::string rF, rL, rP;
|
|
int age;
|
|
double rating, lat, lon;
|
|
ifs >> fName >> lName >> gender >> age >> phone >> rating >> lat >> lon
|
|
>> vehicleType >> state >> rF >> rL >> rP;
|
|
if (!ifs.fail()) {
|
|
//change "null" to empty string
|
|
if (rF == "null") rF = "";
|
|
if (rL == "null") rL = "";
|
|
if (rP == "null") rP = "";
|
|
//create driver
|
|
Driver d(fName, lName, gender, age, phone, rating, lat, lon,
|
|
vehicleType, state, rF, rL, rP);
|
|
drivers.push_back(d);
|
|
}
|
|
}
|
|
ifs.close();
|
|
}
|
|
|
|
void loadRiders(std::ifstream &ifs, std::vector<Rider> &riders) {
|
|
//read the file line by line, total of 17
|
|
while (!ifs.eof()) {
|
|
std::string fName, lName, gender, phone, pickupLoc, dropoffLoc, vPref, state;
|
|
std::string dF, dL, dP;
|
|
int age;
|
|
double rating, pickupLat, pickupLon, dropoffLat, dropoffLon;
|
|
ifs >> fName >> lName >> gender >> age >> phone >> rating
|
|
>> pickupLoc >> pickupLat >> pickupLon
|
|
>> dropoffLoc >> dropoffLat >> dropoffLon
|
|
>> vPref >> state
|
|
>> dF >> dL >> dP;
|
|
if (!ifs.fail()) {
|
|
//fill null with empty string
|
|
if (dF == "null") dF = "";
|
|
if (dL == "null") dL = "";
|
|
if (dP == "null") dP = "";
|
|
//create rider
|
|
Rider r(fName, lName, gender, age, phone, rating,
|
|
pickupLoc, pickupLat, pickupLon,
|
|
dropoffLoc, dropoffLat, dropoffLon,
|
|
vPref, state, dF, dL, dP);
|
|
riders.push_back(r);
|
|
}
|
|
}
|
|
ifs.close();
|
|
}
|
|
|
|
std::ifstream loadFile(const std::string &filename) {
|
|
//read file and return ifstream
|
|
std::ifstream ifs(filename);
|
|
if (!ifs) {
|
|
std::cerr << "Error opening file: " << filename << std::endl;
|
|
exit(1);
|
|
}
|
|
return ifs;
|
|
}
|
|
|
|
void saveFile(const std::string &filename, const std::string &msg) {
|
|
std::ofstream ofs(filename);
|
|
if (!ofs) {
|
|
std::cerr << "Error opening file: " << filename << std::endl;
|
|
exit(1);
|
|
}
|
|
ofs << msg;
|
|
ofs.close();
|
|
}
|
|
|
|
void exportDrivers(const std::string &filename, const std::vector<Driver> &drivers) {
|
|
//save drivers to file
|
|
std::ofstream ofs(filename);
|
|
if (!ofs) {
|
|
std::cerr << "Error opening output file: " << filename << std::endl;
|
|
return;
|
|
}
|
|
for (int i = 0; i < (int)drivers.size(); i++) {
|
|
const Driver &d = drivers[i];
|
|
ofs << d.toFileString() << "\n";
|
|
}
|
|
ofs.close();
|
|
}
|
|
|
|
void exportRiders(const std::string &filename, const std::vector<Rider> &riders) {
|
|
//save riders to file
|
|
std::ofstream ofs(filename);
|
|
if (!ofs) {
|
|
std::cerr << "Error opening output file: " << filename << std::endl;
|
|
return;
|
|
}
|
|
for (int i = 0; i < (int)riders.size(); i++) {
|
|
const Rider &r = riders[i];
|
|
ofs << r.toFileString() << "\n";
|
|
}
|
|
ofs.close();
|
|
}
|
|
|
|
// calculate the distance between two coordinates using Haversine formula
|
|
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
|
|
const double radiusOfEarth = 6371.0; // Earth's radius in kilometers
|
|
// convert latitude and longitude from degrees to radians
|
|
lat1 *= M_PI / 180.0;
|
|
lon1 *= M_PI / 180.0;
|
|
lat2 *= M_PI / 180.0;
|
|
lon2 *= M_PI / 180.0;
|
|
// Haversine formula
|
|
double dLat = lat2 - lat1;
|
|
double dLon = lon2 - lon1;
|
|
double a = sin(dLat / 2.0) * sin(dLat / 2.0) + cos(lat1) * cos(lat2) * sin(dLon / 2.0) * sin(dLon / 2.0);
|
|
double c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));
|
|
// distance in kilometers
|
|
double distanceKM = radiusOfEarth * c;
|
|
// convert it to distance in miles
|
|
double distanceMiles = distanceKM * 0.621371;
|
|
|
|
return distanceMiles;
|
|
}
|
|
|
|
int findClosestDriver(const std::vector<Driver> &drivers,
|
|
const Rider &rider) {
|
|
double minDistance = 1e9;
|
|
int bestIndex = -1;
|
|
for (int i = 0; i < (int)drivers.size(); i++) {
|
|
const Driver &drv = drivers[i];
|
|
if (drv.getCurrentState() == "Available" &&
|
|
drv.getVehicleType() == rider.getVehiclePref()) {
|
|
double dist = calculateDistance(drv.getLatitude(), drv.getLongitude(),
|
|
rider.getPickupLatitude(), rider.getPickupLongitude());
|
|
if (dist < minDistance) {
|
|
minDistance = dist;
|
|
bestIndex = i;
|
|
}
|
|
}
|
|
}
|
|
return bestIndex;
|
|
}
|
|
|
|
int findRiderIndexByPhone(const std::vector<Rider> &riders, const std::string &phone) {
|
|
for (int i = 0; i < (int)riders.size(); i++) {
|
|
if (riders[i].getPhoneNumber() == phone) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int findDriverIndexByPhone(const std::vector<Driver> &drivers, const std::string &phone) {
|
|
for (int i = 0; i < (int)drivers.size(); i++) {
|
|
if (drivers[i].getPhoneNumber() == phone) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
std::string autoAAn(const std::string &word) {
|
|
if (word.empty()) return "";
|
|
if (word[0] == 'A' || word[0] == 'E' || word[0] == 'I' || word[0] == 'O' || word[0] == 'U') {
|
|
return "an";
|
|
}
|
|
return "a";
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
//take 3 arguments
|
|
if (argc < 8) {
|
|
std::cout << "Usage: nyride drivers.txt riders.txt output0.txt output1.txt output2.txt phoneNumber [request|cancel]\n" << std::endl;
|
|
return 1;
|
|
}
|
|
//load arguments
|
|
const std::string drivers_fName = argv[1];
|
|
const std::string riders_fName = argv[2];
|
|
std::string msg_fName = argv[3];
|
|
std::string updated_drivers_fName = argv[4];
|
|
std::string updated_riders_fName = argv[5];
|
|
std::string phone_number = argv[6];
|
|
std::string command = argv[7];
|
|
//turn on debug mode is last argument is debug
|
|
bool debug_mode = false;
|
|
if (std::string(argv[argc - 1]) == "debug") {
|
|
debug_mode = true;
|
|
}
|
|
|
|
if (debug_mode) {
|
|
debug_print("drivers_fName = " + drivers_fName);
|
|
debug_print("riders_fName = " + riders_fName);
|
|
debug_print("msg_fName = " + msg_fName);
|
|
debug_print("updated_drivers_fName = " + updated_drivers_fName);
|
|
debug_print("updated_riders_fName = " + updated_riders_fName);
|
|
debug_print("phone_number = " + phone_number);
|
|
debug_print("command = " + command);
|
|
}
|
|
//load drivers
|
|
std::vector<Driver> drivers;
|
|
std::ifstream drivers_file = loadFile(drivers_fName);
|
|
loadDrivers(drivers_file, drivers);
|
|
//load riders
|
|
std::vector<Rider> riders;
|
|
std::ifstream riders_file = loadFile(riders_fName);
|
|
loadRiders(riders_file, riders);
|
|
if (debug_mode) {
|
|
debug_print("drivers.size() = " + std::to_string(drivers.size()));
|
|
debug_print("riders.size() = " + std::to_string(riders.size()));
|
|
}
|
|
//check if phone number is valid
|
|
std::ostringstream msg;
|
|
if (!isPhoneNumberValid(phone_number)) {
|
|
std::cout << "Error: Invalid phone number" << std::endl;
|
|
msg << "Phone number is invalid.\n";
|
|
saveFile(msg_fName, msg.str());
|
|
return 1;
|
|
}
|
|
//check if command is valid
|
|
if (command != "request" && command != "cancel") {
|
|
std::cout << "Error: Invalid command" << std::endl;
|
|
return 1;
|
|
} else if (command == "request") {
|
|
//for request cases
|
|
int rIdx = findRiderIndexByPhone(riders, phone_number);
|
|
if (rIdx == -1) {
|
|
//if rider does not exist
|
|
msg << "Account does not exist.\n";
|
|
saveFile(msg_fName, msg.str());
|
|
return 1;
|
|
}
|
|
Rider &r = riders[rIdx];
|
|
//check rider
|
|
if (r.getCurrentState() == "Driver_on_the_way") {
|
|
msg << "You have already requested a ride and your driver is on the way to the pickup location.\n";
|
|
saveFile(msg_fName, msg.str());
|
|
return 1;
|
|
}
|
|
if (r.getCurrentState() == "During_the_trip") {
|
|
msg << "You can not request a ride at this moment as you are already on a trip.\n";
|
|
saveFile(msg_fName, msg.str());
|
|
return 1;
|
|
}
|
|
if (r.getCurrentState() == "Ready_to_request") {
|
|
msg << "Ride requested for rider " << r.getFirstName()
|
|
<< ", looking for " << autoAAn(r.getVehiclePref()) << " "
|
|
<< r.getVehiclePref() << " vehicle.\n"
|
|
<< "Pick Up Location: " << r.getPickupLocationName()
|
|
<< ", Drop Off Location: " << r.getDropoffLocationName() << ".\n";
|
|
//find closest driver
|
|
int dIdx = findClosestDriver(drivers, r);
|
|
if (dIdx == -1) {
|
|
//no driver
|
|
msg << "Sorry we can not find a driver for you at this moment.\n";
|
|
} else {
|
|
Driver &d = drivers[dIdx];
|
|
double dist = calculateDistance(d.getLatitude(), d.getLongitude(),
|
|
r.getPickupLatitude(), r.getPickupLongitude());
|
|
dist = (int)(dist * 10) / 10.0; //cut to 1 decimal
|
|
msg << "We have found the closest driver " << d.getFirstName() << "("
|
|
<< std::fixed << std::setprecision(1) << d.getRating() << ") for you.\n"
|
|
<< d.getFirstName() << " is now "
|
|
<< std::fixed << std::setprecision(1) << dist
|
|
<< " miles away from you.\n";
|
|
//update status
|
|
d.setCurrentState("On_the_way_to_pickup");
|
|
d.setRiderInfo(r.getFirstName(), r.getLastName(), r.getPhoneNumber());
|
|
r.setCurrentState("Driver_on_the_way");
|
|
r.setDriverInfo(d.getFirstName(), d.getLastName(), d.getPhoneNumber());
|
|
}
|
|
}
|
|
} else if (command == "cancel") {
|
|
//for cancel cases
|
|
//find phone_number in riders
|
|
int rIdx = findRiderIndexByPhone(riders, phone_number);
|
|
if (rIdx == -1) {
|
|
//in case of driver's cancel
|
|
int dIdx = findDriverIndexByPhone(drivers, phone_number);
|
|
if (dIdx == -1) {
|
|
//in case of not both rider and driver
|
|
msg << "Account does not exist.\n";
|
|
saveFile(msg_fName, msg.str());
|
|
return 1;
|
|
}
|
|
Driver &driver = drivers[dIdx];
|
|
if (driver.getCurrentState() != "On_the_way_to_pickup") {
|
|
msg << "You can only cancel a ride request if you are currently on the way to the pickup location.\n";
|
|
saveFile(msg_fName, msg.str());
|
|
return 1;
|
|
}
|
|
//get rider's phone number
|
|
std::string rPh = driver.getRiderPhoneNumber();
|
|
//clean driver's rider info
|
|
driver.setCurrentState("Available");
|
|
driver.setRiderInfo("", "", "");
|
|
msg << "Your driver " << driver.getFirstName()
|
|
<< " has canceled the ride request. We will now find a new driver for you.\n";
|
|
//find rider
|
|
int theRiderIdx = findRiderIndexByPhone(riders, rPh);
|
|
Rider &r = riders[theRiderIdx];
|
|
//reset rider
|
|
r.setCurrentState("Ready_to_request");
|
|
r.setDriverInfo("", "", "");
|
|
//find a new driver
|
|
msg << "Ride requested for rider " << r.getFirstName()
|
|
<< ", looking for " << autoAAn(r.getVehiclePref()) << " "
|
|
<< r.getVehiclePref() << " vehicle.\n"
|
|
<< "Pick Up Location: " << r.getPickupLocationName()
|
|
<< ", Drop Off Location: " << r.getDropoffLocationName() << ".\n";
|
|
int newDIdx = findClosestDriver(drivers, r);
|
|
if (newDIdx == -1) {
|
|
msg << "Sorry we can not find a driver for you at this moment.\n";
|
|
} else {
|
|
Driver &newDriver = drivers[newDIdx];
|
|
double dist = calculateDistance(newDriver.getLatitude(), newDriver.getLongitude(),
|
|
r.getPickupLatitude(), r.getPickupLongitude());
|
|
dist = (int)(dist * 10) / 10.0; //cut to 1 decimal
|
|
msg << "We have found the closest driver " << newDriver.getFirstName() << "("
|
|
<< std::fixed << std::setprecision(1) << newDriver.getRating() << ") for you.\n"
|
|
<< newDriver.getFirstName() << " is now "
|
|
<< std::fixed << std::setprecision(1) << dist
|
|
<< " miles away from you.\n";
|
|
//update driver's status
|
|
newDriver.setCurrentState("On_the_way_to_pickup");
|
|
newDriver.setRiderInfo(r.getFirstName(), r.getLastName(), r.getPhoneNumber());
|
|
//update rider's status
|
|
r.setCurrentState("Driver_on_the_way");
|
|
r.setDriverInfo(newDriver.getFirstName(), newDriver.getLastName(), newDriver.getPhoneNumber());
|
|
}
|
|
} else {
|
|
//in case of rider's cancel
|
|
Rider &rider = riders[rIdx];
|
|
if (rider.getCurrentState() != "Driver_on_the_way") {
|
|
msg << "You can only cancel a ride request if your driver is currently on the way to the pickup location.\n";
|
|
saveFile(msg_fName, msg.str());
|
|
return 1;
|
|
}
|
|
//find driver's phone_number
|
|
std::string dPh = rider.getDriverPhoneNumber();
|
|
msg << "Ride request for rider " << rider.getFirstName()
|
|
<< " is now canceled by the rider.\n";
|
|
//set driver's status to Available
|
|
int dIdx = findDriverIndexByPhone(drivers, dPh);
|
|
if (dIdx != -1) {
|
|
Driver &drv = drivers[dIdx];
|
|
drv.setCurrentState("Available");
|
|
drv.setRiderInfo("", "", "");
|
|
}
|
|
//set rider's status to Ready_to_request
|
|
rider.setCurrentState("Ready_to_request");
|
|
rider.setDriverInfo("", "", "");
|
|
}
|
|
}
|
|
//save msg
|
|
saveFile(msg_fName, msg.str());
|
|
//save updated drivers and riders
|
|
exportDrivers(updated_drivers_fName, drivers);
|
|
exportRiders(updated_riders_fName, riders);
|
|
|
|
return 0;
|
|
} |