Lesson 1
Analyzing Social Networking App Logs with Ruby
Introduction

Welcome to our new coding practice lesson! We have an interesting problem in this unit that centers around data from a social networking app. The challenge involves processing logs from this app and extracting useful 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 up to n, the total number of groups. Interestingly, the app keeps track of when a group is created and deleted, logging all these actions in a string.

The task before us is to create a Ruby method named analyze_logs. This method will take as input a string of logs and output an array of arrays representing the groups with the longest lifetime. Each inner array contains two items: 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. If multiple groups have the same longest lifetime, the method should return all such groups in ascending order of their IDs.

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 method will return: [[2, '05:00']].

Alternatively, if we have the following log string: "1 create 09:00, 1 delete 11:00, 2 create 10:00, 2 delete 12:00, 3 create 09:30, 3 delete 11:30,", the output should be: [[1, '02:00'], [2, '02:00'], [3, '02:00']].

Solution Building: Step 1

Firstly, we will use Ruby's Time class to handle timestamps and facilitate their operations. After dissecting the input string into individual operations, we use the Time class to parse the timestamps contained in these operations.

Ruby
1def analyze_logs(logs) 2 log_list = logs.split(", ") # Break down the log string into individual logs by splitting 3end
Solution Building: Step 2

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.

Ruby
1def analyze_logs(logs) 2 log_list = logs.split(", ") 3 4 log_list.each do |log| 5 G_ID, action, time = log.split # Splitting each log into group ID, action type, and its happening time 6 end 7end
Solution Building: Step 3

Now that we can identify the action performed on each group and when, it's time to process these details. We convert the group ID into an integer and the timestamp into a Time object. If the log entry marks a 'create' action, we register the time of creation in a hash under the group ID. If the entry signals 'delete,' we calculate the lifetime of the group and store it in another hash.

Ruby
1def analyze_logs(logs) 2 log_list = logs.split(", ") 3 time_hash = {} # Hash to record the creation moment for each group 4 life_hash = Hash.new(0) # Hash to record the lifetime for each group 5 6 log_list.each do |log| 7 G_ID, action, time = log.split 8 G_ID = G_ID.to_i # Converting the group's ID from string to integer 9 time = Time.parse(time) # Converting the timestamp from string to Time object 10 11 if action == 'create' 12 time_hash[G_ID] = time # If the group is created, log the creation time. 13 else 14 if time_hash[G_ID] 15 # If the group is deleted, calculate its total lifetime and remove it from the creation records. 16 life_hash[G_ID] += time - time_hash[G_ID] 17 time_hash.delete(G_ID) 18 end 19 end 20 end 21end

time_hash temporarily stores the creation time of a group, allowing calculation of its lifetime when a delete action is encountered. life_hash accumulates lifetimes for each group. Using Hash.new(0) ensures that accessing an uninitialized key returns 0 instead of nil, enabling direct accumulation.

Solution Building: Step 4

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.

Ruby
1def analyze_logs(logs) 2 log_list = logs.split(", ") 3 time_hash = {} 4 life_hash = Hash.new(0) 5 6 log_list.each do |log| 7 G_ID, action, time = log.split 8 G_ID = G_ID.to_i 9 time = Time.parse(time) 10 11 if action == 'create' 12 time_hash[G_ID] = time 13 else 14 if time_hash[G_ID] 15 life_hash[G_ID] += time - time_hash[G_ID] 16 time_hash.delete(G_ID) 17 end 18 end 19 end 20 21 max_life = life_hash.values.max # Find the longest lifetime 22 # Build the result array where each item is an array of group ID and its lifetime, in string format, if it has the longest lifetime. 23 result = life_hash.select { |_, life| life == max_life }.map do |ID, life| 24 hours, mins = (life / 60 / 60), ((life / 60) % 60) 25 [ID, format('%02d:%02d', hours, mins)] 26 end 27 28 result.sort # Return the array sorted in ascending order of the group IDs 29end

Notice how each create action overwrites the time_hash entry for a group, ensuring only the most recent creation time is stored. Lifetime calculations aggregate durations for all delete actions using the life_hash. If a group is created but not deleted, it remains in time_hash and does not contribute to life_hash. This ensures only fully processed lifetimes are considered.

Lesson Summary

Bravo! You have successfully navigated a non-trivial log analysis problem and worked with timestamped data, a real-world data type in Ruby. Using Ruby's Time class and some clever hash manipulations, you transformed raw strings into meaningful data. Real-life coding often involves accurately understanding, dissecting, and analyzing data, and this unit's lesson has given you practical experience in that regard. Now, let's apply these new learnings to more practice challenges. Keep up the excellent work!

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