add solution for hw8

This commit is contained in:
JamesFlare1212
2025-04-07 17:10:37 -04:00
parent 9d1335bc18
commit 9fd32f72f6
6 changed files with 474 additions and 15 deletions

View File

@@ -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.

View File

@@ -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++;
}

View File

@@ -0,0 +1,41 @@
#ifndef COMMENT_H
#define COMMENT_H
#include <string>
#include <vector>
// 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<Comment*> 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

View File

@@ -0,0 +1,372 @@
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>
#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<std::string, Comment*>& 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<std::string, Comment*>& comment_map,
std::vector<Comment*>& 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<std::string, Comment*>& comment_map,
std::vector<Comment*>& root_comments) {
std::map<std::string, Comment*>::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<std::string, Comment*>::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<std::string, Comment*>& comment_map,
std::vector<Comment*>& 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<std::string, Comment*>& 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<std::string, Comment*>::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<std::string, Comment*>& comment_map) {
std::istringstream iss(line);
std::string command, comment_id;
iss >> command >> comment_id;
// Like the comment
std::map<std::string, Comment*>::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<std::string, Comment*>& comment_map,
std::vector<Comment*>& root_comments) {
std::istringstream iss(line);
std::string command, comment_id;
iss >> command >> comment_id;
// Find the comment to delete
std::map<std::string, Comment*>::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<Comment*>::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<std::string, Comment*>::iterator parent_it = comment_map.find(comment_to_delete->parent_comment_id);
if (parent_it != comment_map.end()) {
std::vector<Comment*>& siblings = parent_it->second->children;
std::vector<Comment*>::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<std::string, Comment*>& 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<std::string, Comment*>::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<std::string, Comment*>& comment_map,
std::vector<Comment*>& 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<Comment*>& 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<std::string, Comment*> comment_map; // Map comment_id to Comment
std::vector<Comment*> 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;
}