Lesson 1
Transforming Social Network Logs into Insights with TypeScript
Introduction

Welcome to our new coding practice lesson! We have an intriguing problem centered around data from a social networking app. The challenge involves processing logs from this app and extracting valuable information from them. This task will leverage your skills in string manipulation, working with timestamps, and task subdivision. Let's get started!

Task Statement

Imagine a social networking application that allows users to form groups. Each group has a unique ID ranging from 1 to n, where n is the total number of groups. The app keeps track of group creation and deletion events, logging all these actions in a string.

The task before us is to create a TypeScript function named analyzeLogs. This function will take as input a string of logs and output an array of strings representing the groups with the longest lifetime. Each string in the array contains two items separated by a space: the group ID and the group's lifetime. By "lifetime," we mean the duration from when the group was created until its deletion. If a group has been created and deleted multiple times, the lifetime is the total sum of those durations. For example, if we have a log string as follows: "1 create 09:00, 2 create 10:00, 1 delete 12:00, 3 create 13:00, 2 delete 15:00, 3 delete 16:00", the function will return: ["2 05:00"].

If multiple groups have the same longest lifetime, the function should return all such groups in ascending order of their IDs. Let's say we have the log string "1 create 08:00, 2 create 09:00, 1 delete 10:00, 2 delete 11:00, 3 create 12:00, 3 delete 13:00". In this case, both group 1 and group 2 have the same lifetime of 02:00. The function should return ["1 02:00", "2 02:00"] since both have the longest lifetime, and group 1 comes before group 2.

Step Overview

To tackle this problem, we will take the following steps:

  • Split the Log Strings: Divide the input string into individual log entries based on a delimiter.
  • Parse Log Components: For each log entry, identify the group ID, action type, and timestamp.
  • Record and Calculate Lifetimes: Track creation times and compute lifetimes whenever a group is deleted.
  • Identify Longest Lifetimes: Compare the lifetimes of all groups to determine the ones with the longest.
Splitting the Log Strings

First, we will split the input string into individual operations. In TypeScript, string manipulation can be handled using methods from the String class, similar to JavaScript, but with added type safety.

TypeScript
1function analyzeLogs(logs: string): string[] { 2 let logList: string[] = logs.split(", ");

The split function in TypeScript is a method of the String class, and it behaves similarly to JavaScript. It will divide the logs string into smaller substrings wherever it finds the delimiter ", " and return an array of those substrings.

TypeScript
1const logs: string = "1 create 09:00, 2 create 10:00, 1 delete 12:00, 3 create 13:00, 2 delete 15:00, 3 delete 16:00"; 2console.log(logs.split(", "));

The output would be:

Plain text
1[ 2 '1 create 09:00', 3 '2 create 10:00', 4 '1 delete 12:00', 5 '3 create 13:00', 6 '2 delete 15:00', 7 '3 delete 16:00' 8]
Parsing Log Components

Next, we delve deeper into the logs. For each logged group operation in the string, we need to parse its components. These include the group ID, the type of operation (create or delete), and the time of action.

TypeScript
1function analyzeLogs(logs: string): string[] { 2 let logList: string[] = logs.split(", "); 3 4 let timeDict: Record<number, [number, number]> = {}; 5 let lifeDict: Record<number, number> = {}; 6 7 logList.forEach((log: string) => { 8 let parts: string[] = log.split(" "); 9 let groupId: number = parseInt(parts[0]); 10 let action: string = parts[1]; 11 let time: string = parts[2]; 12 }); 13}
Recording and Calculating Lifetimes

Now that we can identify each group's actions and their timestamps, it's time to process these details. We convert the group ID into an integer and the timestamp into minutes from the start of the day. If the log entry marks a create action, we register the creation time in an object under the group ID. If the entry signals delete, we calculate the group's lifetime and store it in another object.

Using TypeScript, we can ensure accuracy and readability with type annotations:

TypeScript
1function analyzeLogs(logs: string): string[] { 2 let logList: string[] = logs.split(", "); 3 4 let timeDict: Record<number, [number, number]> = {}; 5 let lifeDict: Record<number, number> = {}; 6 7 logList.forEach((log: string) => { 8 let parts: string[] = log.split(" "); 9 let groupId: number = parseInt(parts[0]); 10 let action: string = parts[1]; 11 let time: string = parts[2]; 12 13 let hour: number = parseInt(time.slice(0, 2)); 14 let minute: number = parseInt(time.slice(3, 5)); 15 let currentTime: number = hour * 60 + minute; 16 17 if (action === "create") { 18 timeDict[groupId] = [hour, minute]; 19 } else { 20 if (groupId in timeDict) { 21 let creationTime = timeDict[groupId][0] * 60 + timeDict[groupId][1]; 22 let lifetime = currentTime - creationTime; 23 if (groupId in lifeDict) { 24 lifeDict[groupId] += lifetime; 25 } else { 26 lifeDict[groupId] = lifetime; 27 } 28 delete timeDict[groupId]; 29 } 30 } 31 });

Steps and Flow:

  1. Each log entry is parsed into its components: group ID, action, and timestamp.
  2. Timestamps are converted into minutes from midnight to simplify arithmetic.
  3. For create actions, the timestamp is recorded in timeDict.
  4. For delete actions, the group's lifetime is calculated using the difference between the deletion time and the recorded creation time. This lifetime is then added to lifeDict.

Result: At the end of this step, lifeDict contains the total lifetimes (in minutes) for all groups that were created and deleted.

Identifying the Longest Lifetimes

After recording the lifetimes of all groups, we can compare them to determine which group or groups had the longest lifetime. Finally, we return the ID or IDs of that group or groups, sorted in ascending order, along with their lifetime.

TypeScript
1function analyzeLogs(logs: string): string[] { 2 let logList: string[] = logs.split(", "); 3 4 let timeDict: Record<number, [number, number]> = {}; 5 let lifeDict: Record<number, number> = {}; 6 7 logList.forEach((log: string) => { 8 let parts: string[] = log.split(" "); 9 let groupId: number = parseInt(parts[0]); 10 let action: string = parts[1]; 11 let time: string = parts[2]; 12 13 let hour: number = parseInt(time.slice(0, 2)); 14 let minute: number = parseInt(time.slice(3, 5)); 15 let currentTime: number = hour * 60 + minute; 16 17 if (action === "create") { 18 timeDict[groupId] = [hour, minute]; 19 } else { 20 if (groupId in timeDict) { 21 let creationTime = timeDict[groupId][0] * 60 + timeDict[groupId][1]; 22 let lifetime = currentTime - creationTime; 23 if (groupId in lifeDict) { 24 lifeDict[groupId] += lifetime; 25 } else { 26 lifeDict[groupId] = lifetime; 27 } 28 delete timeDict[groupId]; 29 } 30 } 31 }); 32 33 let maxLife: number = Math.max(...Object.values(lifeDict)); 34 35 let result: string[] = []; 36 for (let groupId in lifeDict) { 37 if (lifeDict[groupId] === maxLife) { 38 let hours: number = Math.floor(lifeDict[groupId] / 60); 39 let minutes: number = lifeDict[groupId] % 60; 40 let timeString: string = ("0" + hours).slice(-2) + ":" + ("0" + minutes).slice(-2); 41 result.push(groupId + " " + timeString); 42 } 43 } 44 45 return result.sort((a, b) => parseInt(a.split(" ")[0]) - parseInt(b.split(" ")[0])); 46} 47 48// Example usage: 49let logs: string = "1 create 09:00, 2 create 10:00, 1 delete 12:00, 3 create 13:00, 2 delete 15:00, 3 delete 16:00"; 50let result: string[] = analyzeLogs(logs); 51result.forEach((entry: string) => { 52 console.log("Group " + entry.split(" ")[0] + " lifetime: " + entry.split(" ")[1]); 53}); 54// Outputs: 55// Group 2 lifetime: 05:00

Steps and Flow:

  1. Find the maximum lifetime: The longest lifetime is determined by taking the maximum value from lifeDict.
    • Example: From lifeDict = {1: 180, 2: 300, 3: 180}, the maximum lifetime is 300.
  2. Filter groups: Groups with a lifetime matching the maximum are identified.
    • Example: Group 2 matches the maximum lifetime.
  3. Format results: The lifetime is converted from minutes into "HH:MM" format.
    • Example: 300 minutes is formatted as "05:00".
  4. Sort results: The group IDs are sorted in ascending order for the final output.
    • Example: The sorted output is ["2 05:00"].

Result: The function returns an array of group IDs and their respective longest lifetimes in "HH:MM" format. For the example, the final result is ["2 05:00"].

Lesson Summary

Congratulations! You have adeptly navigated a complex log analysis problem while working with timestamped data — a real-world data type — in TypeScript. By utilizing TypeScript's robust type system, you were not only able to transform raw strings into meaningful data but also ensured the reliability and maintainability of your code. In real-world projects, TypeScript's strong typing, class-based object-oriented programming, and meticulous error-checking are invaluable assets, facilitating the creation of cleaner, more predictable applications. Now, let's apply these new learnings to more practice challenges. Off to the races you go!

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