From 9fd32f72f605fc188b563a32c63300bf7ba57d54 Mon Sep 17 00:00:00 2001 From: JamesFlare1212 Date: Mon, 7 Apr 2025 17:10:37 -0400 Subject: [PATCH] add solution for hw8 --- .vscode/launch.json | 25 ++- .vscode/settings.json | 3 +- hws/youtube_comments/README.txt | 13 +- hws/youtube_comments/comment.cpp | 35 +++ hws/youtube_comments/comment.h | 41 ++++ hws/youtube_comments/main.cpp | 372 +++++++++++++++++++++++++++++++ 6 files changed, 474 insertions(+), 15 deletions(-) create mode 100644 hws/youtube_comments/comment.cpp create mode 100644 hws/youtube_comments/comment.h create mode 100644 hws/youtube_comments/main.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 1ab0735..5fa03b5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -106,30 +106,35 @@ "preLaunchTask": "C/C++: g++ build active file" }, { -<<<<<<< HEAD - "name": "lab07", -======= "name": "nysearch", ->>>>>>> 6b2a5ae (solve hw 7) "type": "cppdbg", "request": "launch", "program": "${fileDirname}/${fileBasenameNoExtension}", "args": [ -<<<<<<< HEAD -======= "html_files/index.html", ->>>>>>> 6b2a5ae (solve hw 7) "input.txt" ], "cwd": "${fileDirname}", "environment": [], "MIMode": "gdb", "miDebuggerPath": "/usr/bin/gdb", -<<<<<<< HEAD "preLaunchTask": "C/C++: g++ build single active file" -======= + }, + { + "name": "nycomments", + "type": "cppdbg", + "request": "launch", + "program": "${fileDirname}/${fileBasenameNoExtension}", + "args": [ + "need_you_now.json", + "input1_need_you_now.txt", + "output.txt" + ], + "cwd": "${fileDirname}", + "environment": [], + "MIMode": "gdb", + "miDebuggerPath": "/usr/bin/gdb", "preLaunchTask": "C/C++: g++ build active file" ->>>>>>> 6b2a5ae (solve hw 7) } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 5115d15..072bf29 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -77,6 +77,7 @@ "map": "cpp", "unordered_set": "cpp", "regex": "cpp", - "cinttypes": "cpp" + "cinttypes": "cpp", + "__node_handle": "cpp" } } \ No newline at end of file diff --git a/hws/youtube_comments/README.txt b/hws/youtube_comments/README.txt index 99bf1f2..10bcc1d 100644 --- a/hws/youtube_comments/README.txt +++ b/hws/youtube_comments/README.txt @@ -1,7 +1,7 @@ HOMEWORK 8: Youtube Comments -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 > +FAQ in submitty and some example / tools about JSON 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: 10 hr MISC. COMMENTS TO GRADER: @@ -33,5 +33,10 @@ 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 > +The progress of the entire project has been a bit bumpy. Parsing JSON hasn't +been an issue, but there were some troubles with backslashes. The most +annoying part was the statistics for the total relpy, which due to poor +design made it difficult for me to implement. In the end, I had to +rewrite the comment class, which in turn altered the logic of other +parts; it was really troublesome. diff --git a/hws/youtube_comments/comment.cpp b/hws/youtube_comments/comment.cpp new file mode 100644 index 0000000..10e2854 --- /dev/null +++ b/hws/youtube_comments/comment.cpp @@ -0,0 +1,35 @@ +#include "comment.h" + +// Constructor +Comment::Comment(std::string vid_id, std::string auth, std::string comm_id, int likes, + int replies, bool isReply, std::string parent_id, std::string pub_date, + std::string crawl_date, bool isOwner, std::string comm) { + video_id = vid_id; + author = auth; + comment_id = comm_id; + like_count = likes; + reply_count = replies; + is_reply = isReply; + parent_comment_id = parent_id; + published_date = pub_date; + crawled_date = crawl_date; + is_video_owner = isOwner; + comment = comm; +} + +// Destructor - clean up all children +Comment::~Comment() { + for (size_t i = 0; i < children.size(); ++i) { + delete children[i]; + } +} + +// Add a child comment to this comment +void Comment::addChild(Comment* child) { + children.push_back(child); +} + +// Like this comment +void Comment::like() { + like_count++; +} \ No newline at end of file diff --git a/hws/youtube_comments/comment.h b/hws/youtube_comments/comment.h new file mode 100644 index 0000000..7fae747 --- /dev/null +++ b/hws/youtube_comments/comment.h @@ -0,0 +1,41 @@ +#ifndef COMMENT_H +#define COMMENT_H + +#include +#include + +// Comment class to store information about a YouTube comment +class Comment { +public: + // Comment fields as described in the json file + std::string video_id; + std::string author; + std::string comment_id; + int like_count; + int reply_count; + bool is_reply; + std::string parent_comment_id; + std::string published_date; + std::string crawled_date; + bool is_video_owner; + std::string comment; + + // Vector to store child comments (replies to this comment) + std::vector children; + + // Constructor + Comment(std::string vid_id, std::string auth, std::string comm_id, int likes, + int replies, bool isReply, std::string parent_id, std::string pub_date, + std::string crawl_date, bool isOwner, std::string comm); + + // Destructor - clean up all children + ~Comment(); + + // Add a child comment to this comment + void addChild(Comment* child); + + // Like this comment + void like(); +}; + +#endif // COMMENT_H \ No newline at end of file diff --git a/hws/youtube_comments/main.cpp b/hws/youtube_comments/main.cpp new file mode 100644 index 0000000..9c60b8d --- /dev/null +++ b/hws/youtube_comments/main.cpp @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include +#include +#include "comment.h" + +// Function to parse a string field from json line +std::string parseStringField(const std::string& json, const std::string& field) { + std::string field_name = "\"" + field + "\": \""; + size_t pos = json.find(field_name); + if (pos == std::string::npos) { + return ""; + } + pos += field_name.length(); + size_t end = json.find("\"", pos); + return json.substr(pos, end - pos); +} + +// Function to parse a numeric field from json line +int parseNumericField(const std::string& json, const std::string& field) { + std::string field_name = "\"" + field + "\": "; + size_t pos = json.find(field_name); + if (pos == std::string::npos) { + return 0; + } + pos += field_name.length(); + size_t end = json.find(",", pos); + if (end == std::string::npos) { + end = json.find("}", pos); + } + return std::stoi(json.substr(pos, end - pos)); +} + +// Function to parse a boolean field from json line +bool parseBooleanField(const std::string& json, const std::string& field) { + std::string field_name = "\"" + field + "\": "; + size_t pos = json.find(field_name); + if (pos == std::string::npos) { + return false; + } + pos += field_name.length(); + return json.substr(pos, 4) == "true"; +} + +// Function to recursively display comments with proper indentation +void displayComment(Comment* comment, std::ofstream& output, int indent_level = 0) { + if (comment == nullptr) { + return; + } + + // Create indentation string + std::string indent(indent_level * 4, ' '); + + // Display the comment + output << indent << comment->author << " " << comment->published_date << std::endl; + output << indent << comment->comment << std::endl; + output << indent << "👍 " << comment->like_count << std::endl; + + // Display reply count if there are replies + // We use the original reply_count from the JSON, which includes all descendants + if (comment->reply_count > 0) { + if (comment->reply_count == 1) { + output << indent << comment->reply_count << " reply" << std::endl; + } else { + output << indent << comment->reply_count << " replies" << std::endl; + } + } + + // Recursively display child comments with increased indentation + for (size_t i = 0; i < comment->children.size(); ++i) { + displayComment(comment->children[i], output, indent_level + 1); + } +} + +// Function to recursively delete a comment and all its descendants +void deleteCommentRecursive(Comment* comment, std::map& comment_map) { + if (comment == nullptr) { + return; + } + + // First recursively delete all children + for (size_t i = 0; i < comment->children.size(); ++i) { + deleteCommentRecursive(comment->children[i], comment_map); + } + + // Remove this comment from the map + comment_map.erase(comment->comment_id); +} + +// Function to read and parse the JSON file +void parseJsonFile(const std::string& jsonFile, + std::map& comment_map, + std::vector& root_comments) { + // Open the json file + std::ifstream json_stream(jsonFile); + if (!json_stream.is_open()) { + std::cerr << "Failed to open the JSON file." << std::endl; + exit(1); + } + + // Read the entire json file into a string + std::string json_content; + std::string line; + while (std::getline(json_stream, line)) { + json_content += line; + } + json_stream.close(); + + // Find all comments in the json content + size_t pos = 0; + while ((pos = json_content.find("{\"video_id\":", pos)) != std::string::npos) { + // Find the end of this comment + size_t end = json_content.find("}", pos); + if (end == std::string::npos) break; + end++; // Include the closing brace + + // Extract this comment's json string + std::string comment_json = json_content.substr(pos, end - pos); + + // Parse the fields from the json + std::string video_id = parseStringField(comment_json, "video_id"); + std::string author = parseStringField(comment_json, "author"); + std::string comment_id = parseStringField(comment_json, "comment_id"); + int like_count = parseNumericField(comment_json, "like_count"); + int reply_count = parseNumericField(comment_json, "reply_count"); + bool is_reply = parseBooleanField(comment_json, "is_reply"); + std::string parent_comment_id = parseStringField(comment_json, "parent_comment_id"); + std::string published_date = parseStringField(comment_json, "published_date"); + std::string crawled_date = parseStringField(comment_json, "crawled_date"); + bool is_video_owner = parseBooleanField(comment_json, "is_video_owner"); + std::string comment_text = parseStringField(comment_json, "comment"); + + // Create a new Comment object + Comment* comment = new Comment(video_id, author, comment_id, like_count, reply_count, + is_reply, parent_comment_id, published_date, crawled_date, + is_video_owner, comment_text); + + // Add to the map + comment_map[comment_id] = comment; + + // Move to the next comment + pos = end; + } +} + +// Function to build the comment tree +void buildCommentTree(std::map& comment_map, + std::vector& root_comments) { + std::map::iterator it; + for (it = comment_map.begin(); it != comment_map.end(); ++it) { + Comment* comment = it->second; + if (comment->is_reply) { + // This comment is a reply to another comment + std::map::iterator parent_it = comment_map.find(comment->parent_comment_id); + if (parent_it != comment_map.end()) { + parent_it->second->addChild(comment); + } + } else { + // This comment is a direct response to the video + root_comments.push_back(comment); + } + } +} + +// Function to process a reply to video operation +void processReplyToVideo(const std::string& line, + std::map& comment_map, + std::vector& root_comments) { + std::istringstream iss(line); + std::string command, comment_id, author, comment_text; + iss >> command >> comment_id >> author; + + // Skip whitespace to get to the comment text + iss >> std::ws; + // Read the comment text (enclosed in double quotes) + if (iss.peek() == '"') { + iss.get(); // Consume the opening double quote + std::getline(iss, comment_text, '"'); // Read until the closing double quote + } + + // Create a new comment + Comment* new_comment = new Comment("", author, comment_id, 0, 0, false, "", "0 seconds ago", "", false, comment_text); + + // Add to the map and root comments + comment_map[comment_id] = new_comment; + root_comments.push_back(new_comment); +} + +// Function to process a reply to comment operation +void processReplyToComment(const std::string& line, + std::map& comment_map) { + std::istringstream iss(line); + std::string command, parent_id, comment_id, author, comment_text; + iss >> command >> parent_id >> comment_id >> author; + + // Skip whitespace to get to the comment text + iss >> std::ws; + // Read the comment text (enclosed in double quotes) + if (iss.peek() == '"') { + iss.get(); // Consume the opening double quote + std::getline(iss, comment_text, '"'); // Read until the closing double quote + } + + // Create a new comment + Comment* new_comment = new Comment("", author, comment_id, 0, 0, true, parent_id, "0 seconds ago", "", false, comment_text); + + // Add to the map + comment_map[comment_id] = new_comment; + + // Add as a child to the parent comment + std::map::iterator parent_it = comment_map.find(parent_id); + if (parent_it != comment_map.end()) { + parent_it->second->addChild(new_comment); + // Increment the parent's reply_count + parent_it->second->reply_count++; + } +} + +// Function to process a like comment operation +void processLikeComment(const std::string& line, + std::map& comment_map) { + std::istringstream iss(line); + std::string command, comment_id; + iss >> command >> comment_id; + + // Like the comment + std::map::iterator it = comment_map.find(comment_id); + if (it != comment_map.end()) { + it->second->like(); + } +} + +// Function to process a delete comment operation +void processDeleteComment(const std::string& line, + std::map& comment_map, + std::vector& root_comments) { + std::istringstream iss(line); + std::string command, comment_id; + iss >> command >> comment_id; + + // Find the comment to delete + std::map::iterator it = comment_map.find(comment_id); + if (it != comment_map.end()) { + Comment* comment_to_delete = it->second; + + // If this is a root comment, remove it from root_comments + std::vector::iterator root_it = std::find(root_comments.begin(), root_comments.end(), comment_to_delete); + if (root_it != root_comments.end()) { + root_comments.erase(root_it); + } else { + // If not a root comment, remove it from its parent's children + std::map::iterator parent_it = comment_map.find(comment_to_delete->parent_comment_id); + if (parent_it != comment_map.end()) { + std::vector& siblings = parent_it->second->children; + std::vector::iterator sibling_it = std::find(siblings.begin(), siblings.end(), comment_to_delete); + if (sibling_it != siblings.end()) { + siblings.erase(sibling_it); + // Decrement the parent's reply count + parent_it->second->reply_count--; + } + } + } + + // Recursively delete this comment and all its descendants + deleteCommentRecursive(comment_to_delete, comment_map); + delete comment_to_delete; + } +} + +// Function to process a display comment operation +void processDisplayComment(const std::string& line, + std::map& comment_map, + std::ofstream& output_file) { + std::istringstream iss(line); + std::string command, comment_id; + iss >> command >> comment_id; + + // Display the comment and its descendants + std::map::iterator it = comment_map.find(comment_id); + if (it != comment_map.end()) { + displayComment(it->second, output_file); + } +} + +// Function to process all operations from the operations file +void processOperations(const std::string& operationsFile, + const std::string& outputFile, + std::map& comment_map, + std::vector& root_comments) { + // Open the operations file + std::ifstream ops_file(operationsFile); + if (!ops_file.is_open()) { + std::cerr << "Failed to open the operations file." << std::endl; + exit(1); + } + + // Open the output file + std::ofstream output_file(outputFile); + if (!output_file.is_open()) { + std::cerr << "Failed to open the output file." << std::endl; + exit(1); + } + + // Process each operation in the operations file + std::string line; + while (std::getline(ops_file, line)) { + std::istringstream iss(line); + std::string command; + iss >> command; + + if (command == "reply_to_video") { + processReplyToVideo(line, comment_map, root_comments); + } + else if (command == "reply_to_comment") { + processReplyToComment(line, comment_map); + } + else if (command == "like_comment") { + processLikeComment(line, comment_map); + } + else if (command == "delete_comment") { + processDeleteComment(line, comment_map, root_comments); + } + else if (command == "display_comment") { + processDisplayComment(line, comment_map, output_file); + } + } + + // Close files + ops_file.close(); + output_file.close(); +} + +// Function to clean up memory +void cleanupMemory(std::vector& root_comments) { + for (size_t i = 0; i < root_comments.size(); ++i) { + delete root_comments[i]; + } +} + +int main(int argc, char* argv[]) { + // Check if the correct number of command line arguments is provided + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " input1.json input2.txt output.txt" << std::endl; + return 1; + } + + // Parse command line arguments + std::string jsonFile = argv[1]; + std::string operationsFile = argv[2]; + std::string outputFile = argv[3]; + + // Data structures to store comments + std::map comment_map; // Map comment_id to Comment + std::vector root_comments; // Comments that are direct responses to the video + + // Parse the JSON file + parseJsonFile(jsonFile, comment_map, root_comments); + + // Build the comment tree + buildCommentTree(comment_map, root_comments); + + // Process operations + processOperations(operationsFile, outputFile, comment_map, root_comments); + + // Clean up memory + cleanupMemory(root_comments); + + return 0; +} \ No newline at end of file