Each year Crossfit headquarters has a large global competition which they call the Crossfit Open. The Open is meant to be an extremely accessible competition for people in the Crossfit community who wouldn’t typically think about entering a competition. The result is often extremely motivating to the participants because they prove themselves capable of things that they previously thought they would never be capable of.
Okay, okay, you’re probably wondering what the hell this has to do with programming. Well, let me explain because the best ideas usually come from something you are passionate about.
I mentioned that this competition is annual and has been going on for almost a decade now.
Traditionally, one workout each year is programmed to be done which is a repeat of a workout that was done during a previous year. The idea is that if you have already done that workout you’d try to beat your previous score. That’s where I was struck with my great idea!
The repeat workout this year was announced and as a coach, judge, athlete, and long-time veteran of the sports side of Crossfit, I immediately began strategizing for others. Eventually, I was writing out pacing-plans for athletes who had already done the workout, and around the second time that I began crunching numbers for athletes, I decided it’d just be easier to create a calculator.
For now, this calculator will take the form of an algorithmic problem stored locally on my computer, as you will soon see, but I eventually would like to publish an app that can calculate an average pace for any ‘Multi-modal general physical preparedness training’ (the non-copyrighted title for Crossfit. Sometimes just abbreviated as ‘GPP’).
So the first thing we need to do is describe the problem. We have a workout with repetitions and we want to be able to complete all of those repetitions in a certain amount of time. Often enough, the repetitions in a workout are varied so we can break them up into rounds.
The rounds and reps of a given workout can be most concisely represented as an array. The array represents a logical progression where the indexes can represent the rounds in our workout and integers found at each index in the array can represent the number of repetitions in each round.
Then there will be the target time to complete the workout, which we can represent as an integer.
The first thing that should be done is to find how long each repetition in the workout can take and to do this we need to know how many repetitions there is total in the workout and, since there are more repetitions in this workout than there are minutes to complete (as is the case 99% of the time with workouts that can have a pace) we can convert our target time into seconds since it was passed in as an input representing minutes. The seconds a repetition can take would then be the number of seconds we have divided by the total number of repetitions we need to perform.
Now that we know how many seconds each repetition can take we can multiply the reps we have to perform each round by that number to get how many seconds each round can take.
This is all the data we need however, it should be converted into a format that is more readable for the end-user because, like this, it isn’t all that useful. After all, whose going to sit there counting the seconds, resetting the seconds’ clock each round? The clock which the athlete, judge, coaches, spectators, and all others involved will be looking at is a running clock with minutes and seconds. So, a couple of things need to happen, for the data to be usable.
First, each round should be the time elapsed thus far in the workout. That is each round is represented by the time that round can take plus the time all the previous rounds can take.
Second, each round should be represented as time in minutes as opposed to seconds, to match the workout clock. The athlete working out should be able to glance at notes on a whiteboard and then the clock to quickly determine whether to go faster or slower. The athlete is likely going through repetitions quickly and/or might not be able to think about it later in an intense workout so the coach, judge, and spectators should have this same ability to track the athlete’s pace at a glance.
Perfect, that’s exactly what we want. So now that we have fully described the problem, how can we take our input for our wodPace algorithm and return the data in the desired format (described above) so that we can later display that information to our end-user on the app?
Well, first, our output should be an array with the accumulated time in minutes for each progressive round in the workout.
Also, the targetTime should only be a whole positive integer. This is because we can only move forward in time and also because at some point in our algorithm rounding needs to occur anyways so the additional accuracy is not worth the additional overhead (there will never be more than a 1 or 2 seconds deviation each round.
Ok, to start we have a function called wodPace that takes in a workout, which is an array representing the rounds and exercise repetitions in the given workout, and we have a targetTime which is a positive whole integer representing the time in which we are trying to finish the workout in. And from these inputs, our output will be an array of strings, each element formatted to be understood by the end-user, each element representing the max time the athlete has to complete each respective round of the workout (which is represented by the indexes of the input array). The elements of the output array will look like [‘1:00’, ‘2:00’…].
After that, we can take inventory of all the items we have discussed already:
- We’ll create the variable pace to add to as we iterate and change the values from workout and finally to return at the end.
- We’ll create a variable elapsedTime which will act as an accumulator by having the time in seconds of each round added to it.
- We’ll also create the variables reps and targetSeconds to abstract away some of the calculations we have to perform on our inputs and to hold the sum of the values in workout and targetSeconds which represents the total seconds in targetTime respectively.
- Lastly, we’ll create the variable repTime to calculate the max time in seconds each repetition can take and we’ll use our previous variables reps and targetTime to calculate that.
From here, we can create a loop to begin iterating over, the workout array, and making the changes we need on each value to push into our pace array.
I still need to add to this for loop though. To do this, I decided to take a functional approach and break the formatting out into another formula that way I can take care of converting and adjusting my output separate from my main function. I found it was getting a little congested and I try to keep things readable.
Here, I find my minutes and seconds separately, account for no zero seconds (I want all times to show consistently with two trailing digits), and then I combine my results in a string literal. Note that I converted everything into strings. The results I come up with in this solution are intended to be the results that will be used to render to the end-user (by that I mean, I don’t intend to perform any further calculations on this data), so this is fine for me.
Then after we combine everything and return our pace array we have our desired output. An array, mimicking to the end-user, a running clock they need to beat in order to meet the pace they chose (our targetTime input).
This solution iterates over workout twice so it retains an O(n) time complexity where n is the workout array. We are also creating an array, pace, that is relative to the length of our workout array and so this solution maintains an O(n) space complexity.
As always, thanks for your time and I hope you learned something. If you are looking for a challenge you can refactor this solution to account for partial number arguments to the targetTime input. Additionally, you can also find a refactor of this solution with improved time complexity.
If you have any comments, questions, or remarks, please feel free to reach out.