Files
CSCI-1200/hws/online_dating/nydate.cpp
2025-03-12 10:01:26 -04:00

415 lines
14 KiB
C++

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <cmath>
#include <vector>
#include <algorithm>
// Node representing a user in the linked list
class User {
public:
std::string name;
int age;
std::string gender;
std::string phone;
std::string profession;
std::string school;
double latitude;
double longitude;
bool premium;
int prefMinAge;
int prefMaxAge;
int maxDistance;
std::string prefGender;
std::vector<std::string> likes; // list of phone numbers this user liked
User* next;
};
// Helper function to convert underscores to spaces in a string
std::string formatString(const std::string &s) {
std::string result = "";
for (std::size_t i = 0; i < s.size(); i++) {
if (s[i] == '_')
result.push_back(' ');
else
result.push_back(s[i]);
}
return result;
}
// Calculate the distance between two coordinates using the Haversine formula
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
const double radiusOfEarth = 6371.0; // Earth's radius in kilometers
// Convert degrees to radians
lat1 = lat1 * M_PI / 180.0;
lon1 = lon1 * M_PI / 180.0;
lat2 = lat2 * M_PI / 180.0;
lon2 = lon2 * M_PI / 180.0;
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));
double distanceKM = radiusOfEarth * c;
double distanceMiles = distanceKM * 0.621371;
return distanceMiles;
}
// Splits a string by the underscore delimiter
std::vector<std::string> splitLikes(const std::string &s) {
std::vector<std::string> result;
std::istringstream iss(s);
std::string token;
while (std::getline(iss, token, '_')) {
result.push_back(token);
}
return result;
}
// Loads the users from the given file and returns the head of the linked list
User* loadUsers(const std::string &filename) {
std::ifstream fin(filename.c_str());
if (!fin) {
std::cerr << "Error opening file: " << filename << std::endl;
return 0;
}
User* head = 0;
User* tail = 0;
std::string line;
while (std::getline(fin, line)) {
if (line.empty())
continue;
std::istringstream iss(line);
User* newUser = new User;
newUser->next = 0;
std::string premiumStr, likesStr;
// Read the 14 fields
iss >> newUser->name
>> newUser->age
>> newUser->gender
>> newUser->phone
>> newUser->profession
>> newUser->school
>> newUser->latitude
>> newUser->longitude
>> premiumStr
>> newUser->prefMinAge
>> newUser->prefMaxAge
>> newUser->maxDistance
>> newUser->prefGender
>> likesStr;
// Convert premium field
newUser->premium = (premiumStr == "true");
// Process likes: if "null", then empty; else split by underscore
if (likesStr != "null") {
newUser->likes = splitLikes(likesStr);
}
// Append to linked list
if (head == 0) {
head = newUser;
tail = newUser;
} else {
tail->next = newUser;
tail = newUser;
}
}
fin.close();
return head;
}
// Frees the memory allocated for the linked list
void freeUsers(User* head) {
while (head) {
User* temp = head;
head = head->next;
delete temp;
}
}
// Finds and returns the pointer to the user with the given phone number
User* findUser(User* head, const std::string &phone) {
User* curr = head;
while (curr) {
if (curr->phone == phone)
return curr;
curr = curr->next;
}
return 0;
}
// Checks whether a given phone number exists in the likes vector of a user
bool isNumberInLikes(const User* user, const std::string &phone) {
std::vector<std::string>::const_iterator it;
for (it = user->likes.begin(); it != user->likes.end(); ++it) {
if (*it == phone)
return true;
}
return false;
}
// Removes the given phone number from the user's likes vector
void removeLike(User* user, const std::string &phone) {
std::vector<std::string>::iterator it;
for (it = user->likes.begin(); it != user->likes.end(); ++it) {
if (*it == phone) {
user->likes.erase(it);
break;
}
}
}
// Checks whether candidate matches the current user's preference
bool matchesPreference(const User* current, const User* candidate) {
// Exclude self
if (current->phone == candidate->phone)
return false;
// Check age
if (candidate->age < current->prefMinAge || candidate->age > current->prefMaxAge)
return false;
// Check gender preference
if (current->prefGender != "Both" && candidate->gender != current->prefGender)
return false;
// Check distance
double dist = calculateDistance(current->latitude, current->longitude, candidate->latitude, candidate->longitude);
if (dist > current->maxDistance)
return false;
return true;
}
// Prints a single profile to the output stream
void printProfile(std::ofstream &out, const User* user) {
out << user->name << " " << user->age;
if (user->profession != "Undisclosed")
out << std::endl << formatString(user->profession);
if (user->school != "Undisclosed")
out << std::endl << formatString(user->school);
out << std::endl;
}
// Returns a vector of users who match the current user's preference
// If excludePhone is nonempty, then any user with that phone is skipped
std::vector<User*> getMatchingProfiles(const User* current, const User* head, const std::string &excludePhone = "") {
std::vector<User*> result;
const User* curr = head;
while (curr) {
if (excludePhone != "" && curr->phone == excludePhone) {
curr = curr->next;
continue;
}
if (matchesPreference(current, curr))
result.push_back(const_cast<User*>(curr));
curr = curr->next;
}
return result;
}
// Returns a vector of users who mutually match with the current user
// A mutual match is defined as current liking candidate and candidate liking current
std::vector<User*> getMatches(const User* current, const User* head) {
std::vector<User*> result;
const User* curr = head;
while (curr) {
if (current->phone == curr->phone) {
curr = curr->next;
continue;
}
if (isNumberInLikes(current, curr->phone) && isNumberInLikes(curr, current->phone))
result.push_back(const_cast<User*>(curr));
curr = curr->next;
}
return result;
}
// Returns a vector of users who liked the current user
std::vector<User*> getLikes(const User* current, const User* head) {
std::vector<User*> result;
const User* curr = head;
while (curr) {
if (current->phone == curr->phone) {
curr = curr->next;
continue;
}
if (isNumberInLikes(curr, current->phone))
result.push_back(const_cast<User*>(curr));
curr = curr->next;
}
return result;
}
// Command: show profiles (or for block command, with an exclusion)
void commandShowProfiles(const User* current, const User* head, std::ofstream &out, const std::string &excludePhone = "") {
std::vector<User*> profiles = getMatchingProfiles(current, head, excludePhone);
if (profiles.empty()) {
out << "There are no users matching with your preference at this moment." << std::endl;
return;
}
const User* curr = head;
bool first = true;
while (curr) {
if ((excludePhone == "" || curr->phone != excludePhone) && matchesPreference(current, curr)) {
if (!first) out << std::endl;
printProfile(out, curr);
first = false;
}
curr = curr->next;
}
}
// Command: show matches. Matches should be sorted by phone (increasing order)
void commandShowMatches(const User* current, const User* head, std::ofstream &out) {
std::vector<User*> matches = getMatches(current, head);
if (matches.empty()) {
out << "You do not have any matches at this moment.";
return;
}
std::sort(matches.begin(), matches.end(),
[](User* a, User* b) -> bool { return a->phone < b->phone; });
for (size_t i = 0; i < matches.size(); ++i) {
if (i > 0) out << std::endl;
printProfile(out, matches[i]);
}
}
// Command: show likes. Only premium users can use this
void commandShowLikes(const User* current, const User* head, std::ofstream &out) {
if (!current->premium) {
out << "Only premium users can view who liked you.";
return;
}
std::vector<User*> likes = getLikes(current, head);
if (likes.empty()) {
out << "You have not received any likes so far.";
return;
}
const User* curr = head;
bool first = true;
while (curr) {
if (current->phone != curr->phone && isNumberInLikes(curr, current->phone)) {
if (!first) out << std::endl;
printProfile(out, curr);
first = false;
}
curr = curr->next;
}
}
// Command: unmatch someone. Remove mutual like between current and other,
// then print both users' match lists (sorted by phone number)
void commandUnmatch(User* current, User* other, const User* head, std::ofstream &out) {
removeLike(current, other->phone);
removeLike(other, current->phone);
std::vector<User*> currentMatches = getMatches(current, head);
std::vector<User*> otherMatches = getMatches(other, head);
std::sort(currentMatches.begin(), currentMatches.end(),
[](User* a, User* b) -> bool { return a->phone < b->phone; });
std::sort(otherMatches.begin(), otherMatches.end(),
[](User* a, User* b) -> bool { return a->phone < b->phone; });
out << current->name << "'s match list:" << std::endl << std::endl;
if (currentMatches.empty()) {
out << "You do not have any matches at this moment." << std::endl;
} else {
for (size_t i = 0; i < currentMatches.size(); ++i) {
if (i > 0) out << std::endl;
printProfile(out, currentMatches[i]);
}
}
out << std::endl << "======" << std::endl << std::endl;
out << other->name << "'s match list:" << std::endl << std::endl;
if (otherMatches.empty()) {
out << "You do not have any matches at this moment." << std::endl;
} else {
for (size_t i = 0; i < otherMatches.size(); ++i) {
if (i > 0) out << std::endl;
printProfile(out, otherMatches[i]);
}
}
}
// Command: block someone. For the current user and the other user, print profiles
// that match their preference but exclude the blocked user
void commandBlock(const User* current, const User* other, const User* head, std::ofstream &out) {
out << "profiles shown to " << current->name << ":" << std::endl << std::endl;
commandShowProfiles(current, head, out, other->phone);
out << std::endl << "======" << std::endl << std::endl;
out << "profiles shown to " << other->name << ":" << std::endl << std::endl;
commandShowProfiles(other, head, out, current->phone);
}
int main(int argc, char* argv[]) {
if (argc < 5) {
std::cerr << "Usage: nydate.exe users.txt output.txt phoneNumber command [phoneNumberOther]" << std::endl;
return 1;
}
std::string usersFile = argv[1];
std::string outputFile = argv[2];
std::string currentPhone = argv[3];
std::string command = argv[4];
User* head = loadUsers(usersFile);
if (head == 0) {
return 1;
}
User* currentUser = findUser(head, currentPhone);
if (currentUser == 0) {
std::cerr << "User with phone " << currentPhone << " not found." << std::endl;
freeUsers(head);
return 1;
}
std::ofstream fout(outputFile.c_str());
if (!fout) {
std::cerr << "Error opening output file: " << outputFile << std::endl;
freeUsers(head);
return 1;
}
// Process commands
if (command == "profile") {
commandShowProfiles(currentUser, head, fout);
} else if (command == "match") {
commandShowMatches(currentUser, head, fout);
} else if (command == "like") {
commandShowLikes(currentUser, head, fout);
} else if (command == "unmatch") {
if (argc < 6) {
std::cerr << "unmatch command requires a second phone number." << std::endl;
freeUsers(head);
return 1;
}
std::string otherPhone = argv[5];
User* otherUser = findUser(head, otherPhone);
if (otherUser == 0) {
std::cerr << "User with phone " << otherPhone << " not found." << std::endl;
freeUsers(head);
return 1;
}
commandUnmatch(currentUser, otherUser, head, fout);
} else if (command == "block") {
if (argc < 6) {
std::cerr << "block command requires a second phone number." << std::endl;
freeUsers(head);
return 1;
}
std::string otherPhone = argv[5];
User* otherUser = findUser(head, otherPhone);
if (otherUser == 0) {
std::cerr << "User with phone " << otherPhone << " not found." << std::endl;
freeUsers(head);
return 1;
}
commandBlock(currentUser, otherUser, head, fout);
} else {
std::cerr << "Unknown command: " << command << std::endl;
}
fout.close();
freeUsers(head);
return 0;
}