add solution for hw 5

This commit is contained in:
JamesFlare1212
2025-03-12 10:00:49 -04:00
parent c20ce7c2c3
commit 903e76ef1f
6 changed files with 772 additions and 326 deletions

View File

@@ -1,7 +1,7 @@
HOMEWORK 5: Online Dating
NAME: < insert name >
NAME: Jinshan Zhou
COLLABORATORS AND OTHER RESOURCES:
@@ -10,13 +10,13 @@ List the names of everyone you talked to about this assignment
LMS, etc.), and all of the resources (books, online reference
material, etc.) you consulted in completing this assignment.
< insert collaborators / resources >
Stack Overflow' cases about some pointer further examples
Remember: Your implementation for this assignment must be done on your
own, as described in "Academic Integrity for Homework" handout.
ESTIMATE OF # OF HOURS SPENT ON THIS ASSIGNMENT: < insert # hours >
ESTIMATE OF # OF HOURS SPENT ON THIS ASSIGNMENT: 8 hr
MISC. COMMENTS TO GRADER:
@@ -33,5 +33,7 @@ What parts of the assignment did you find challenging? Is there anything that
finally "clicked" for you in the process of working on this assignment? How well
did the development and testing process go for you?
< insert reflection >
For this assignment, it combine a lot of concept from serval lecture. Like I used
class, pointer, the iostream library, etc. It was quite challenging to me at
first. Since the link list is not very familiar to me, I spend some time on
the lab 5 assignment and online resource to understand it better.

View 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;
}