#include #include #include #include #include #include #include // 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 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 splitLikes(const std::string &s) { std::vector 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::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::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 getMatchingProfiles(const User* current, const User* head, const std::string &excludePhone = "") { std::vector 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(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 getMatches(const User* current, const User* head) { std::vector 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(curr)); curr = curr->next; } return result; } // Returns a vector of users who liked the current user std::vector getLikes(const User* current, const User* head) { std::vector 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(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 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 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 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 currentMatches = getMatches(current, head); std::vector 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; }