Welcome to today's lesson on applying data filtering and aggregation in a real-world scenario using a user management system in Java. We'll start by building a foundational structure to handle basic user operations. Then, we'll expand it by introducing more advanced functionalities that allow for filtering and aggregating user data.
In our starter task, we will implement a class that manages basic operations on a collection of user data, specifically handling adding new users, retrieving user profiles, and updating user profiles.
Here are the starter task methods:
addUser(String userId, int age, String country, boolean subscribed)
- adds a new user with the specified attributes. Returnstrue
if the user was added successfully andfalse
if a user with the sameuserId
already exists.getUser(String userId)
- returns the user's profile as an object if the user exists; otherwise, returnsnull
.updateUser(String userId, Integer age, String country, Boolean subscribed)
- updates the user's profile based on non-null parameters. Returnstrue
if the user exists and was updated,false
otherwise.
The Java implementation of our starter task is shown below:
Java1import java.util.HashMap; 2import java.util.Map; 3 4public class UserManager { 5 private Map<String, UserProfile> users; 6 7 public UserManager() { 8 users = new HashMap<>(); 9 } 10 11 public boolean addUser(String userId, int age, String country, boolean subscribed) { 12 if (users.containsKey(userId)) { 13 return false; 14 } 15 users.put(userId, new UserProfile(age, country, subscribed)); 16 return true; 17 } 18 19 public UserProfile getUser(String userId) { 20 return users.get(userId); 21 } 22 23 public boolean updateUser(String userId, Integer age, String country, Boolean subscribed) { 24 UserProfile profile = users.get(userId); 25 if (profile == null) { 26 return false; 27 } 28 if (age != null) { 29 profile.setAge(age); 30 } 31 if (country != null) { 32 profile.setCountry(country); 33 } 34 if (subscribed != null) { 35 profile.setSubscribed(subscribed); 36 } 37 return true; 38 } 39} 40 41class UserProfile { 42 private int age; 43 private String country; 44 private boolean subscribed; 45 46 public UserProfile(int age, String country, boolean subscribed) { 47 this.age = age; 48 this.country = country; 49 this.subscribed = subscribed; 50 } 51 52 public int getAge() { 53 return age; 54 } 55 56 public void setAge(int age) { 57 this.age = age; 58 } 59 60 public String getCountry() { 61 return country; 62 } 63 64 public void setCountry(String country) { 65 this.country = country; 66 } 67 68 public boolean isSubscribed() { 69 return subscribed; 70 } 71 72 public void setSubscribed(boolean subscribed) { 73 this.subscribed = subscribed; 74 } 75 76 @Override 77 public String toString() { 78 return "UserProfile{" + "age=" + age + ", country='" + country + '\'' + ", subscribed=" + subscribed + '}'; 79 } 80} 81 82class Program { 83 public static void main(String[] args) { 84 UserManager um = new UserManager(); 85 System.out.println(um.addUser("u1", 25, "USA", true)); // true, Adds new user 86 System.out.println(um.addUser("u2", 30, "Canada", false)); // true, Adds another user 87 System.out.println(um.addUser("u1", 22, "Mexico", true)); // false, User exists 88 System.out.println(um.getUser("u1")); // UserProfile with specified attributes 89 System.out.println(um.updateUser("u1", 26, null, null)); // true, Only updates age 90 System.out.println(um.updateUser("u3", 19, "UK", false)); // false, User does not exist 91 System.out.println(um.updateUser("u1", null, "UK", true)); // true, Updates country and subscription status 92 } 93}
The updateUser
method allows for partial updates of a user's profile. Nullable parameters (age, country, and subscribed) enable selective updates, meaning only the provided non-null values are used to modify the existing user data. This design avoids unnecessary data updates and improves efficiency, especially in scenarios where only a subset of the user's data changes frequently.
This implementation covers all our starter methods. Let's move forward and introduce more complex functionalities.
With our foundational structure in place, it's time to add functionalities for filtering user data and aggregating statistics.
Here are new methods to implement:
filterUsers(Integer minAge, Integer maxAge, String country, Boolean subscribed)
:- Returns the list of user IDs that match the specified criteria. Criteria can be
null
, meaning that the criterion should not be applied during filtering.
- Returns the list of user IDs that match the specified criteria. Criteria can be
aggregateStats()
- returns statistics in the form of an object:totalUsers
: Total number of usersaverageAge
: Average age of all users (rounded down to the nearest integer)subscribedRatio
: Ratio of subscribed users to total users (as a float with two decimals)
This method filters users based on the criteria provided. Let's see how it works:
Java1import java.util.ArrayList; 2import java.util.List; 3import java.util.Map; 4 5public class UserManager { 6 // Existing methods... 7 8 // Method to filter users based on criteria 9 public List<String> filterUsers(Integer minAge, Integer maxAge, String country, Boolean subscribed) { 10 List<String> filteredUsers = new ArrayList<>(); 11 12 for (Map.Entry<String, UserProfile> entry : users.entrySet()) { 13 String userId = entry.getKey(); 14 UserProfile profile = entry.getValue(); 15 16 // Check minimum age criterion 17 if (minAge != null && profile.getAge() < minAge) { 18 continue; 19 } 20 // Check maximum age criterion 21 if (maxAge != null && profile.getAge() > maxAge) { 22 continue; 23 } 24 // Check country criterion 25 if (country != null && !profile.getCountry().equals(country)) { 26 continue; 27 } 28 // Check subscription status criterion 29 if (subscribed != null && profile.isSubscribed() != subscribed) { 30 continue; 31 } 32 filteredUsers.add(userId); 33 } 34 return filteredUsers; 35 } 36} 37 38class Program { 39 public static void main(String[] args) { 40 UserManager um = new UserManager(); 41 System.out.println(um.addUser("u1", 25, "USA", true)); // true 42 System.out.println(um.addUser("u2", 30, "Canada", false)); // true 43 System.out.println(um.addUser("u1", 22, "Mexico", true)); // false 44 System.out.println(um.getUser("u1")); // UserProfile with specified attributes 45 System.out.println(um.updateUser("u1", 26, null, null)); // true 46 System.out.println(um.updateUser("u3", 19, "UK", false)); // false 47 48 System.out.println(um.filterUsers(20, 30, "USA", true)); // [u1] 49 } 50}
- The
filterUsers
method filters users based onminAge
,maxAge
,country
, andsubscribed
status criteria. - It iterates over the
users
map and checks each user's profile against the provided criteria. - Users who meet all the criteria are added to the
filteredUsers
list, which is then returned. - The example usage shows how to add users and filter them based on different criteria.
This method aggregates statistics from the user profiles. Let's implement it:
Java1import java.util.Map; 2 3public class UserManager { 4 // Existing methods... 5 6 // Method to aggregate statistics from user profiles 7 public Stats aggregateStats() { 8 int totalUsers = users.size(); 9 if (totalUsers == 0) { 10 return new Stats(0, 0, 0.00); 11 } 12 13 // Calculate total age by summing ages of all users 14 int totalAge = 0; 15 int subscribedUsers = 0; 16 for (UserProfile profile : users.values()) { 17 totalAge += profile.getAge(); 18 if (profile.isSubscribed()) { 19 subscribedUsers++; 20 } 21 } 22 23 // Calculate average age (rounded down) 24 int averageAge = totalAge / totalUsers; 25 // Calculate subscribed ratio (to two decimals) 26 double subscribedRatio = Math.round(((double) subscribedUsers / totalUsers) * 100) / 100.0; 27 28 return new Stats(totalUsers, averageAge, subscribedRatio); 29 } 30} 31 32class Stats { 33 private int totalUsers; 34 private int averageAge; 35 private double subscribedRatio; 36 37 public Stats(int totalUsers, int averageAge, double subscribedRatio) { 38 this.totalUsers = totalUsers; 39 this.averageAge = averageAge; 40 this.subscribedRatio = subscribedRatio; 41 } 42 43 public int getTotalUsers() { 44 return totalUsers; 45 } 46 47 public int getAverageAge() { 48 return averageAge; 49 } 50 51 public double getSubscribedRatio() { 52 return subscribedRatio; 53 } 54} 55 56class Program { 57 public static void main(String[] args) { 58 UserManager um = new UserManager(); 59 System.out.println(um.addUser("u1", 25, "USA", true)); // true 60 System.out.println(um.addUser("u2", 30, "Canada", false)); // true 61 System.out.println(um.addUser("u1", 22, "Mexico", true)); // false 62 System.out.println(um.getUser("u1")); // UserProfile with specified attributes 63 System.out.println(um.updateUser("u1", 26, null, null)); // true 64 System.out.println(um.updateUser("u3", 19, "UK", false)); // false 65 66 System.out.println(um.filterUsers(20, 30, "USA", true)); // [u1] 67 Stats stats = um.aggregateStats(); 68 System.out.println(stats.getTotalUsers()); // 2 69 System.out.println(stats.getAverageAge()); // 27 70 System.out.println(stats.getSubscribedRatio()); // 0.50 71 } 72}
The aggregateStats
method calculates and returns aggregate statistics about the users in the form of a Stats
class object. It first determines totalUsers
, the total number of users. If there are no users, it returns an object with zeroed statistics. Otherwise, it calculates totalAge
by summing the ages of all users and counts subscribedUsers
who are subscribed. It then computes averageAge
and calculates subscribedRatio
using integer and floating-point arithmetic. The resulting statistics object includes totalUsers
, averageAge
, and subscribedRatio
.
Great job! Today, you've learned how to effectively handle user data by implementing advanced functionalities like filtering and aggregation on top of a basic system using Java. These are critical skills in real-life software development, where you often need to extend existing systems to meet new requirements.
I encourage you to practice solving similar challenges to solidify your understanding of data filtering and aggregation. Happy coding, and see you in the next lesson!