add solution for hw 5
This commit is contained in:
415
hws/online_dating/nydate.cpp
Normal file
415
hws/online_dating/nydate.cpp
Normal file
@@ -0,0 +1,415 @@
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user