We'll be using JavaScript, but this should work for any language that uses the modulo operator
TL;DR
Sometimes you have a range of numbers like 1-10. You want functions to increment and decrement the index within the range without going below the minimum or above the maximum. The modulo operator accomplishes this.
Use Case
I was recently following along the guide to Stimulus.js and ran across this problem.
If you're interested, Stimulus is a JavaScript framework that comes standard in Ruby on Rails 7. Since I like to code in Ruby and use Rails, I've been learning it. It allows you to easily manipulate DOM elements with Javascript by adding data attributes to your elements.
The guide walked through creating slideshow-like functionality where two buttons toggle through a list of four divs that either display or are hidden.
The problem is that when the current index is 0, and you click previous, the index decrements to -1. Similarly, if you are on index 3 (the fourth item in the list), the index increments to 5. The desired behavior would be for it to wrap around to the first/last elements.
increment
0 → 1 → 2 → 3 → 0 → 1
decrement
2 ← 3 ← 0 ← 1 ← 2 ← 3
You get the idea!
How to Accomplish this using Javascript!
// let's start by defining a simple class Range
class Range {
constructor(index) {
this.index = index;
}
// here are our two methods. We will return the index
// so that it's easy to see what's happening
next() {
this.index++
return this.index
}
previous() {
this.index--
return this.index
}
}
So the goal here would be to instantiate a new instance of Range, and use the two methods increment and decrement the index, looping through 0-4, never going below zero or above four. Of course... this won't work.
// our class allows us to specify the starting range.
// we could have set the default to 0 as well.
let range = new Range(0);
Now let's try calling the methods and see what happens to the index.
range.index
=> 0
range.next()
=> 1
range.next()
=> 2
range.index
=> 2
// cool! it's works
range.next()
=> 3
range.next()
=> 4
range.next()
=> 5
range.next()
=> 6
// oops! too high!
// let's bring it down.
range.previous()
=> 5
range.previous()
=> 4
range.previous()
=> 3
range.previous()
=> 2
range.previous()
=> 1
range.previous()
=> 0
range.previous()
=> -1
range.previous()
=> -2
// oops! too low!
So hopefully now what we are trying to accomplish is clear. Now let's get to the solution.
Using the Modulo Operator
What is it? TL;DR it returns the remainder after division.
3 divided by 2 is 1 with a remainder of 1. What if we only want the remainder value? Use our friend modulo. It's just the percent sign.
3 % 2 => 1
10 % 8 => 2
5 % 5 => 0
4 % 2 => 0
6 % 3 => 0
7 % 4 => 3
156 % 37 => 8
Whenever you use modulo with a larger number on the right, it will return the number on the left.
1 % 10 => 1
1 % 2 => 1
5 % 6 => 5
5 % 10000 => 5
Modulo can actually accomplish some cool things! I first saw a practical use for it while trying to code a 2D spaceship game in Ruby. The modulo operator allowed the spaceship to go off the screen and re-enter from the other side. It's going to serve a similar purpose here.
Let's say my index is 0. I want to add 1 to it but not allow it to get above the number 4. So 5 is the length of the range 0,1,2,3,4.
So what I do is this, I assign the original index (0) to the sum of itself plus the range length (5) plus the increment amount (1) modulo the range length (5).
index = (index + rangeLength + incrementAmount) % rangeLength
Mind blown yet? 🤯
Try it out!
Assign the original index to the sum of itself plus the range length plus the increment modulo the range length.
Correction! When incrementing, you don't need to add the range length. It doesn't hurt if you do though. So you could just do index = (index + 1) % rangeLength. You do need to add the range length when decrementing though.
index = 0
index = (0 + 5 + 1) % 5 => 1
Six divided by five has a remainder of 1. So now the index is 1.
index = (1 + 5 + 1) % 5 => 2
index = (2 + 5 + 1) % 5 => 3
index = (3 + 5 + 1) % 5 => 4
index = (4 + 5 + 1) % 5 => 0
It worked! Because 10 divided by 5 has no remainder the index now restarted at 0.
Now let's try the decrement method. Just assign the index to the remainder of itself plus the range length minus the decrement amount divided by the range length.
index = (index + rangeLength - decrementAmount) % rangeLength
let index = 4
index = (4 + 5 - 1) % 5 => 3
index = (3 + 5 - 1) % 5 => 2
index = (2 + 5 - 1) % 5 => 1
index = (1 + 5 - 1) % 5 => 0
index = (0 + 5 - 1) % 5 => 4
Now we can edit our Range class to implement this.
class Range {
constructor(index) {
this.index = index;
this.length = 5;
this.incrementAmount = 1;
this.decrementAmount = 1;
}
next() {
this.index = (this.index + this.length + this.incrementAmount) % this.length;
return this.index
}
previous() {
this.index = (this.index + this.length - this.decrementAmount) % this.length;
return this.index
}
}
Now test it out. Our next()
and previous()
methods should work perfectly!
Top comments (0)