add solution for hw8
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
35
hws/youtube_comments/comment.cpp
Normal file
35
hws/youtube_comments/comment.cpp
Normal 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++;
|
||||
}
|
||||
41
hws/youtube_comments/comment.h
Normal file
41
hws/youtube_comments/comment.h
Normal 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
|
||||
372
hws/youtube_comments/main.cpp
Normal file
372
hws/youtube_comments/main.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user