Lesson 1
Managing Product Reviews in Go with Data Aggregation
Introduction

Hello, and welcome to today's lesson! Today, we are going to dive into the world of managing product reviews and applying data aggregation in practice. We will start with a relatively simple Starter Task to set up our codebase, and then gradually build up to a more complex solution involving data aggregation. Let's jump in!

Starter Task: Structures and Functions

For our starter task, we'll lay the foundation by implementing basic operations for managing product reviews. The Review struct encapsulates the details of a product review. Here's a breakdown of the fields:

  • text string — The textual content of the review.
  • rating int — The rating score of the review, ranging from 1 to 5.
  • flagged bool — A boolean indicating whether the review is flagged as inappropriate.

The individual reviews are managed via a ReviewManager. These are the functions we'll implement in our ReviewManager for the starter task:

  • addReview(productId string, reviewId string, reviewText string, rating int) bool — Adds a review to the product specified by productId. If a review with reviewId already exists, it updates the existing review. Returns true if the review was added or updated successfully, false otherwise. Newly added reviews have the flagged attribute set to false initially.

  • getReview(productId string, reviewId string) (Review, bool) — Returns the review details (text, rating, and flagged fields) for the review specified by reviewId under the given productId. The second return value indicates if the review was found.

  • deleteReview(productId string, reviewId string) bool — Deletes the review specified by reviewId under the given productId. Returns true if the review was deleted, false otherwise.

Starter Task Implementation

Let's look at the code that implements these functionalities:

Go
1package main 2 3import "fmt" 4 5type Review struct { 6 text string 7 rating int 8 flagged bool 9} 10 11type ReviewManager struct { 12 products map[string]map[string]Review 13} 14 15func NewReviewManager() *ReviewManager { 16 return &ReviewManager{products: make(map[string]map[string]Review)} 17} 18 19func (rm *ReviewManager) addReview(productId, reviewId, reviewText string, rating int) bool { 20 if rating < 1 || rating > 5 { 21 return false // Invalid rating 22 } 23 24 if _, exists := rm.products[productId]; !exists { 25 rm.products[productId] = make(map[string]Review) 26 } 27 rm.products[productId][reviewId] = Review{reviewText, rating, false} 28 return true 29} 30 31func (rm *ReviewManager) getReview(productId, reviewId string) (Review, bool) { 32 if productReviews, exists := rm.products[productId]; exists { 33 if review, found := productReviews[reviewId]; found { 34 return review, true 35 } 36 } 37 return Review{}, false 38} 39 40func (rm *ReviewManager) deleteReview(productId, reviewId string) bool { 41 if productReviews, exists := rm.products[productId]; exists { 42 if _, found := productReviews[reviewId]; found { 43 delete(productReviews, reviewId) 44 if len(productReviews) == 0 { 45 delete(rm.products, productId) // Remove product if no reviews left 46 } 47 return true 48 } 49 } 50 return false 51} 52 53func main() { 54 reviewManager := NewReviewManager() 55 56 // Adding some reviews 57 reviewManager.addReview("p1", "r1", "Great product!", 5) 58 reviewManager.addReview("p1", "r2", "Not bad", 3) 59 60 // Testing getReview method 61 if review, found := reviewManager.getReview("p1", "r1"); found { 62 fmt.Printf("Review r1 for p1: {text: %s, rating: %d, flagged: %v}\n", review.text, review.rating, review.flagged) 63 } else { 64 fmt.Println("Review r1 for p1 not found") 65 } 66 67 if _, found := reviewManager.getReview("p1", "r3"); found { 68 fmt.Println("Review r3 for p1 found") 69 } else { 70 fmt.Println("Review r3 for p1 not found") 71 } 72 73 // Testing deleteReview method 74 if reviewManager.deleteReview("p1", "r2") { 75 fmt.Println("Review r2 for p1 deleted successfully") 76 } else { 77 fmt.Println("Failed to delete review r2 for p1") 78 } 79 80 if _, found := reviewManager.getReview("p1", "r2"); found { 81 fmt.Println("Review r2 for p1 found") 82 } else { 83 fmt.Println("Review r2 for p1 not found") 84 } 85}

This code establishes the foundational functions needed for managing product reviews within a ReviewManager. The addReview function allows for adding a new review or updating an existing one, ensuring each review contains valid rating values between 1 and 5. The getReview function retrieves the review details for a specific product, returning a boolean to indicate whether the product or review exists. The deleteReview function handles the removal of specific reviews, and if no other reviews remain for a product, the product itself is removed from the list.

Now, let's extend this with new features.

New Task: Advanced Functions and Data Aggregation

With our basic review management system in place, we will now introduce new functions to handle more complex operations, such as flagging inappropriate reviews and aggregating review data for a specific product.

Here are the new functions we’ll add:

  • flagReview(productId string, reviewId string) bool — This function flags a specific review as inappropriate for a given product. Returns true if the review was successfully flagged, false otherwise.

  • aggregateReviews(productId string) (AggregatedData, bool) — This function aggregates review data for a given product, providing statistics such as the total number of reviews, the number of flagged reviews, average rating, and review texts excluding flagged ones. The second return value is a boolean indicating if there was data to aggregate.

Step 1: Adding the 'flagReview' Function

First, let's add functionality to flag a review:

Go
1// ... previous code 2 3func (rm *ReviewManager) flagReview(productId, reviewId string) bool { 4 if productReviews, exists := rm.products[productId]; exists { 5 if review, found := productReviews[reviewId]; found { 6 review.flagged = true 7 productReviews[reviewId] = review 8 return true 9 } 10 } 11 return false 12} 13 14func main() { 15 reviewManager := NewReviewManager() 16 17 // Adding some reviews 18 reviewManager.addReview("p1", "r1", "Great product!", 5) 19 reviewManager.addReview("p1", "r2", "Not bad", 3) 20 reviewManager.addReview("p1", "r3", "Terrible", 1) 21 22 // Flagging a review 23 reviewManager.flagReview("p1", "r3") 24 25 // Testing flagReview method 26 if review, found := reviewManager.getReview("p1", "r3"); found { 27 fmt.Printf("Review r3 for p1: {text: %s, rating: %d, flagged: %v}\n", review.text, review.rating, review.flagged) 28 } else { 29 fmt.Println("Review r3 for p1 not found") 30 } 31}

In this step, we are adding the flagReview function to our ReviewManager. This function allows users to mark a specific review as inappropriate. It checks whether the product and review exist in our data structures, and if they do, it sets the flagged attribute of the review to true. Flagging is important for maintaining review quality.

Step 2: Adding the 'aggregateReviews' Function

Next, we will implement the function to aggregate reviews:

Go
1// ... previous code 2 3type AggregatedData struct { 4 totalReviews int 5 flaggedReviews int 6 averageRating float64 7 reviewTexts []string 8} 9 10func (rm *ReviewManager) aggregateReviews(productId string) (AggregatedData, bool) { 11 if productReviews, exists := rm.products[productId]; exists && len(productReviews) > 0 { 12 var totalReviews, flaggedReviews, totalRating int 13 var reviewTexts []string 14 15 for _, review := range productReviews { 16 totalReviews++ 17 if review.flagged { 18 flaggedReviews++ 19 } else { 20 totalRating += review.rating 21 reviewTexts = append(reviewTexts, review.text) 22 } 23 } 24 25 averageRating := 0.0 26 if totalReviews != flaggedReviews { 27 averageRating = float64(totalRating) / float64(totalReviews-flaggedReviews) 28 } 29 30 return AggregatedData{totalReviews, flaggedReviews, averageRating, reviewTexts}, true 31 } 32 return AggregatedData{}, false 33} 34 35func main() { 36 reviewManager := NewReviewManager() 37 38 // Adding some reviews 39 reviewManager.addReview("p1", "r1", "Great product!", 5) 40 reviewManager.addReview("p1", "r2", "Not bad", 3) 41 reviewManager.addReview("p1", "r3", "Terrible", 1) 42 43 // Flagging a review 44 reviewManager.flagReview("p1", "r3") 45 46 // Testing the aggregation method 47 if data, success := reviewManager.aggregateReviews("p1"); success { 48 fmt.Printf("Aggregated data for p1: {total_reviews: %d,flagged_reviews: %d,average_rating: %.1f,review_texts: %v}\n", data.totalReviews, data.flaggedReviews, data.averageRating, data.reviewTexts) 49 } else { 50 fmt.Println("No aggregated data for p1") 51 } // Aggregated data for p1: {total_reviews: 3,flagged_reviews: 1,average_rating: 4.0,review_texts: [Great product! Not bad]} 52}

Let's breakdown this code:

  • The aggregateReviews function is added to ReviewManager to collect review statistics for a product.
  • It utilizes the AggregatedData struct to store total reviews, flagged reviews, average rating, and non-flagged review texts.
  • The function ensures the product exists and has reviews to aggregate.
  • It iterates over reviews, updating total and flagged review counts, and calculates the total rating for non-flagged reviews.
  • The average rating is computed from non-flagged reviews, defaulting to zero if all are flagged.
  • Finally, it returns an AggregatedData struct with the calculated statistics and a success indicator.
Conclusion

Great job! Today, you have learned how to manage product reviews and apply data aggregation using Go. We started with basic operations for adding, retrieving, and deleting reviews. Then, we extended our functionality to include flagging reviews and aggregating review data. This gradual build-up demonstrates how to enhance features incrementally and handle more complex data aggregation tasks.

Feel free to practice solving similar challenges to strengthen your skills further. Keep coding, and see you in the next lesson!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.