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"]

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

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"]]]

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

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"]]]

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

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"]]]

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" }

giving us:

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

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")

resulting in:

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

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")

giving us:

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

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")

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

Let's Work Together

We would love to hear from you so let's get in touch!

CONTACT US TODAY!