Welcome to today's lesson on applying data filtering and aggregation in a real-world scenario using a user management system. We'll start by building a foundational structure that can handle basic user operations. Then, we'll expand it by introducing more advanced functionalities that allow 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(userId, age, country, 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(userId)
- returns the user's profile as an object if the user exists; otherwise, returnsnull
.updateUser(userId, age, country, subscribed)
- updates the user's profile based on non-null
parameters. Returnstrue
if the user exists and was updated,false
otherwise.
The JavaScript implementation of our starter task is shown below:
JavaScript1class UserManager { 2 constructor() { 3 this.users = {}; // Initialize an empty object to store user data 4 } 5 6 // Method to add a new user 7 addUser(userId, age, country, subscribed) { 8 if (this.users[userId]) { 9 return false; // Return false if userId already exists 10 } 11 this.users[userId] = { age, country, subscribed }; // Add user to the users object 12 return true; // Return true to indicate successful addition 13 } 14 15 // Method to retrieve a user's profile 16 getUser(userId) { 17 return this.users[userId] || null; // Return the user profile or null if the user does not exist 18 } 19 20 // Method to update a user's profile 21 updateUser(userId, age, country, subscribed) { 22 if (!this.users[userId]) { 23 return false; // Return false if the user does not exist 24 } 25 if (age !== null) { 26 this.users[userId].age = age; // Update age if provided 27 } 28 if (country !== null) { 29 this.users[userId].country = country; // Update country if provided 30 } 31 if (subscribed !== null) { 32 this.users[userId].subscribed = subscribed; // Update subscription status if provided 33 } 34 return true; // Return true to indicate successful update 35 } 36} 37 38// Example usage 39const um = new UserManager(); 40console.log(um.addUser("u1", 25, "USA", true)); // true 41console.log(um.addUser("u2", 30, "Canada", false)); // true 42console.log(um.addUser("u1", 22, "Mexico", true)); // false 43console.log(um.getUser("u1")); // { age: 25, country: "USA", subscribed: true } 44console.log(um.updateUser("u1", 26, null, null)); // true 45console.log(um.updateUser("u3", 19, "UK", false)); // false
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(minAge, maxAge, country, 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:
JavaScript1class UserManager { 2 // Existing methods... 3 4 // Method to filter users based on criteria 5 filterUsers(minAge, maxAge, country, subscribed) { 6 const filteredUsers = []; 7 for (const [userId, profile] of Object.entries(this.users)) { 8 // Check minimum age criterion 9 if (minAge !== null && profile.age < minAge) { 10 continue; 11 } 12 // Check maximum age criterion 13 if (maxAge !== null && profile.age > maxAge) { 14 continue; 15 } 16 // Check country criterion 17 if (country !== null && profile.country !== country) { 18 continue; 19 } 20 // Check subscription status criterion 21 if (subscribed !== null && profile.subscribed !== subscribed) { 22 continue; 23 } 24 // Add userId to filteredUsers if all criteria are met 25 filteredUsers.push(userId); 26 } 27 return filteredUsers; // Return the list of filtered user IDs 28 } 29} 30 31// Example usage of the new method 32const um = new UserManager(); 33um.addUser("u1", 25, "USA", true); 34um.addUser("u2", 30, "Canada", false); 35um.addUser("u3", 22, "USA", true); 36console.log(um.filterUsers(20, 30, "USA", true)); // ["u1", "u3"] 37console.log(um.filterUsers(null, 28, null, null)); // ["u1", "u3"] 38console.log(um.filterUsers(null, null, "Canada", false)); // ["u2"]
- The
filterUsers
method filters users based onminAge
,maxAge
,country
, andsubscribed
status criteria. - It iterates over the
users
dictionary 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:
JavaScript1class UserManager { 2 // Existing methods... 3 4 // Method to aggregate statistics from user profiles 5 aggregateStats() { 6 const totalUsers = Object.keys(this.users).length; // Get the total number of users 7 if (totalUsers === 0) { // If no users, return zeroed statistics 8 return { totalUsers: 0, averageAge: 0, subscribedRatio: 0.00 }; 9 } 10 11 // Calculate total age by summing ages of all users 12 const totalAge = Object.values(this.users).reduce((sum, profile) => sum + profile.age, 0); 13 // Count number of subscribed users 14 const subscribedUsers = Object.values(this.users).filter(profile => profile.subscribed).length; 15 16 // Calculate average age (rounded down) 17 const averageAge = Math.floor(totalAge / totalUsers); 18 // Calculate subscribed ratio (to two decimals) 19 const subscribedRatio = (subscribedUsers / totalUsers).toFixed(2); 20 21 return { totalUsers, averageAge, subscribedRatio: parseFloat(subscribedRatio) }; // Return statistics object 22 } 23} 24 25// Using `um` from the previous section 26console.log(um.aggregateStats()); // { totalUsers: 3, averageAge: 25, subscribedRatio: 0.67 }
This aggregateStats
method calculates and returns aggregate statistics about the users in the form of an 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
by performing integer division of totalAge
by totalUsers
and calculates subscribedRatio
by dividing subscribedUsers
by totalUsers
and rounding to two decimals. The resulting statistics object includes totalUsers
, averageAge
, and subscribedRatio
.
Here's the complete UserManager
class with all methods, including the new ones for filtering and aggregation:
JavaScript1class UserManager { 2 constructor() { 3 this.users = {}; // Initialize an empty object to store user data 4 } 5 6 // Method to add a new user 7 addUser(userId, age, country, subscribed) { 8 if (this.users[userId]) { 9 return false; // Return false if userId already exists 10 } 11 this.users[userId] = { age, country, subscribed }; // Add user to the users object 12 return true; // Return true to indicate successful addition 13 } 14 15 // Method to retrieve a user's profile 16 getUser(userId) { 17 return this.users[userId] || null; // Return the user profile or null if the user does not exist 18 } 19 20 // Method to update a user's profile 21 updateUser(userId, age, country, subscribed) { 22 if (!this.users[userId]) { 23 return false; // Return false if the user does not exist 24 } 25 if (age !== null) { 26 this.users[userId].age = age; // Update age if provided 27 } 28 if (country !== null) { 29 this.users[userId].country = country; // Update country if provided 30 } 31 if (subscribed !== null) { 32 this.users[userId].subscribed = subscribed; // Update subscription status if provided 33 } 34 return true; // Return true to indicate successful update 35 } 36 37 // Method to filter users based on criteria 38 filterUsers(minAge, maxAge, country, subscribed) { 39 const filteredUsers = []; 40 for (const [userId, profile] of Object.entries(this.users)) { 41 // Check minimum age criterion 42 if (minAge !== null && profile.age < minAge) { 43 continue; 44 } 45 // Check maximum age criterion 46 if (maxAge !== null && profile.age > maxAge) { 47 continue; 48 } 49 // Check country criterion 50 if (country !== null && profile.country !== country) { 51 continue; 52 } 53 // Check subscription status criterion 54 if (subscribed !== null && profile.subscribed !== subscribed) { 55 continue; 56 } 57 // Add userId to filteredUsers if all criteria are met 58 filteredUsers.push(userId); 59 } 60 return filteredUsers; // Return the list of filtered user IDs 61 } 62 63 // Method to aggregate statistics from user profiles 64 aggregateStats() { 65 const totalUsers = Object.keys(this.users).length; // Get the total number of users 66 if (totalUsers === 0) { // If no users, return zeroed statistics 67 return { totalUsers: 0, averageAge: 0, subscribedRatio: 0.00 }; 68 } 69 70 // Calculate total age by summing ages of all users 71 const totalAge = Object.values(this.users).reduce((sum, profile) => sum + profile.age, 0); 72 // Count number of subscribed users 73 const subscribedUsers = Object.values(this.users).filter(profile => profile.subscribed).length; 74 75 // Calculate average age (rounded down) 76 const averageAge = Math.floor(totalAge / totalUsers); 77 // Calculate subscribed ratio (to two decimals) 78 const subscribedRatio = (subscribedUsers / totalUsers).toFixed(2); 79 80 return { totalUsers, averageAge, subscribedRatio: parseFloat(subscribedRatio) }; // Return statistics object 81 } 82} 83 84// Example usage 85const um = new UserManager(); 86um.addUser("u1", 25, "USA", true); 87um.addUser("u2", 30, "Canada", false); 88um.addUser("u3", 22, "USA", true); 89 90console.log(um.filterUsers(20, 30, "USA", true)); // ["u1", "u3"] 91console.log(um.filterUsers(null, 28, null, null)); // ["u1", "u3"] 92console.log(um.filterUsers(null, null, "Canada", false)); // ["u2"] 93 94console.log(um.aggregateStats()); // { totalUsers: 3, averageAge: 25, subscribedRatio: 0.67 }
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. This is a critical skill 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!