Recently I was faced with an interesting problem. I needed to create a simplified (pseudo-) cron interface:
The task is to find the next selected day.
My first attempt was a naive one. Loop to the end of the array and if nothing was found, start a second loop at the beginning and move to the current day (pseudocode, no error detection, performance optimization or edge casing):
$days = Array(false, true, false, true, false, false, false); $today = 4 // Thursday $i = today + 1; // loop index starts at the next day (Friday) $next = -1; // loop abort variable // iterate to the end of the array while ($i <= $today.length() && $next == -1) { if ($days[i] == true) { // day checkbox is selected $next = $days[$i]; } $i++; } // no truthy element was found if ($next == -1) { $i = 0; // start at the beginninf of the array // iterate from the start to the current day while ($i <= $today && $next == -1) { if ($days[i] == true) { // day checkbox is selected $next = $i; } $i++; } } print $next; // 1 (Monday)
The problem can be abstracted: Printing all the indices of an array in sequence, using a variable starting point. In the example above:
.5, 6, 0, 1, 2, 3, 4
$start = 4; $arr = Array(false, true, false, false, true, false, false); $max = $arr.length; $i = $start + 1 ; while ($i < $max) { print ($i); // 5, 6 $i++; } $i = 0; while ($i < $start + 1) { print ($i); // 0, 1, 2, 3, 4 $i++; }
If you look at the output, you’ll notice that the numbers are all integers and less than or equal to `max
.
There’s a special, lesser-known operator that comes in handy in this case: modulo. In a nutshell, modulo returns the integer remainder of a division, e.g:
- 1 mod 5 → 1 (0 remaining 1)
- 2 mod 5 → 2 (0 remaining 2)
- 3 mod 5 → 3 (0 remaining 3)
- 4 mod 5 → 4 (0 remaining 4)
- 5 mod 5 → 0 (1 remaining 0)
- 6 mod 5 → 1 (1 remaining 1)
- 7 mod 5 → 2 (1 remaining 2)
You will notice two things:
- the result is always less than or equal to the dividend (
5
) - the result is repeated after the dividend (
1, 2, 3, 4, 5, 6, 7
) exceeds the value of the divisor (5
).
That’s exactly what we need. Let’s put something like this, with different numbers, into code:
$start = 4; $max = 7; $i = $start; while ($i < $max + $start) { print ($i); // 4, 5, 6, 7, 8, 9, 10 print ($i mod $max); // 4, 5, 6, 0, 1, 2, 3 $i++; }
The index i
exceeds the length of the array, but we’re using i mod max
and not the loop index itself. The result makes a round trip after reaching max
and starts again at 0
. All indexes of the array are covered, starting from the middle index
.4
The actual code now looks like this:
$days = Array(false, true, false, true, false, false, false); $today = 4 // THursday $i = $today + 1; $max = $days.length; $next = -1; while ($i < $max + $today && $next == -1) { if ($days[$i mod $max] == true) { $next = i mod $max; } $i++; } print ($next); // 1 (Monday)
This code may not look as readable as before, so why are we doing this?
Because we can!
And it’s fun to come up with a clever solution to a problem. We’re programmers, after all.
Foto by Philip Veater on Unsplash