Exercising During Lockdown II: a look at Ruby's Enumerable chunk method

Another way Ruby can help you keep track of your lockdown exercise routine

Another way Ruby can help you keep track of your lockdown exercise routine

Like Charlotte in the previous post, Deborah has had to make some changes to her exercise routine due to the coronavirus lockdown. She can only exercise outside by herself, and can’t go to the gym at all. She’s been keeping a record of what she’s done each day in an Array:

daily_workout_log = ["Weights: squats", "weights: benchpresses", "weights: deadlifts", "Rest", "Rest", "Bike: 25km", "Run: 10km", "Run: 5km", "bike: 50km", "BIKE: 30km"]
Ruby

Deborah would like to summarise this in an easy-to-read format, leaving out the details of each exercise session; something along the lines of: “Weights: 3 days; Rest: 2 days” etc

This looks like a job for Ruby’s Enumerable chunk method. This will group together consecutive items based on the value returned by the supplied block. If that doesn’t make much sense, an example might help:

daily_workout_log.chunk{ |item| item }.to_a
Ruby

would result in:

[["Weights: squats", ["Weights: squats"]], ["Weights: benchpresses", ["Weights: benchpresses"]], ["Weights: deadlifts", ["Weights: deadlifts"]], ["Rest", ["Rest", "Rest"]], ["Bike: 25km", ["Bike: 25km"]], ["Run: 10km", ["Run: 10km"]], ["Run: 5km", ["Run: 5km"]], ["bike: 50km", ["bike: 50km"]], ["BIKE: 30km", ["BIKE: 30km"]]]
Console

That might look a bit like an unhelpful mess of nested arrays when you first look at it, and I’d find it hard to disagree. Note, however, that the two days where Deborah recorded as “rest” have been combined. Our next step is going to be to remove the details of each exercise session, so that, for example, the three weight sessions are grouped together. We can do this by using split to ignore anything after the colon:

daily_workout_log.chunk{ |item| item.split(":").first }.to_a
Ruby

which will give us:

[["Weights", ["Weights: squats", "Weights: benchpresses", "Weights: deadlifts"]], ["Rest", ["Rest", "Rest"]], ["Bike", ["Bike: 25km"]], ["Run", ["Run: 10km", "Run: 5km"]], ["bike", ["bike: 50km"]], ["BIKE", ["BIKE: 30km"]]]
Console

This is a bit of an improvement; the consecutive weight sessions have been grouped together, as have the consecutive running sessions. Deborah has been a bit sloppy when recording her bike sessions: she’s entered “bike: 50km”, and then “BIKE: 30km”, and our code is treating these as different types of exercise. That’s easily fixed with capitalize:

daily_workout_log.chunk{ |item| item.split(":").first.capitalize }.to_a
Ruby

resulting in:

[["Weights", ["Weights: squats", "Weights: benchpresses", "Weights: deadlifts"]], ["Rest", ["Rest", "Rest"]], ["Bike", ["Bike: 25km"]], ["Run", ["Run: 10km", "Run: 5km"]], ["Bike", ["bike: 50km", "BIKE: 30km"]]]
Console

Next task will be to calculate the totals for each type of exercise:

daily_workout_log.chunk{ |item| item.split(":").first.capitalize }.to_a.map{ |type, list| "#{type}: #{list.size} days" }
Ruby

giving us:

["Weights: 3 days", "Rest: 2 days", "Bike: 1 days", "Run: 2 days", "Bike: 2 days"]
Console

And we can convert this into a report by simply using join:

daily_workout_log.chunk{ |item| item.split(":").first.capitalize }.to_a.map{ |type, list| "#{type}: #{list.size} days" }.join("\n")
Ruby

resulting in:

"Weights: 2 days
Rest: 3 days
Bike: 1 days
Run: 2 days
Bike: 2 days"
Console

That’s looking pretty good, but let’s go a step further, and ensure that we produce “1 day” instead of the ugly “1 days”. We can do this in a concise manner using the ternary operator:

daily_workout_log.chunk{ |item| item.split(":").first.capitalize }.to_a.map{ |type, list| "#{type}: #{list.size} #{list.one? ? "days" : "day"}" }.join("\n")
Ruby

giving us:

"Weights: 2 days
Rest: 3 days
Bike: 1 day
Run: 2 days
Bike: 2 days"
Console

We’re done - Deborah will now be able to view an easy-to-read summary of her exercise activity.

Realistically, we probably wouldn’t want to do this in a single line: instead we should use helpfully named methods and intermediate variables to make it clearer what’s going on. For example:

def extract_exercise_type(item)
  item.split(":").first.capitalize
end

def summary_line(type, list)
  "#{type}: #{list.size} #{list.one? ? "days" : "day"}"
end

chunked_log = daily_workout_log.chunk{ |item| extract_exercise_type(item) }.to_a
chunked_log.map{ |type, list| summary_line(type, list) }.join("\n")
Ruby

I’d love to hear your thoughts on chunk, the uses you’ve found for it, and indeed, exercising during lockdown. Why not leave a comment below?


Related Posts