How can I modulo when my numbers start from 1, not zero?

I guess the solution for this is quite simple, but I've been thinking about it for a while and couldn't come up with an elegant solution.

I have a range of numbers, e.g. 1..10 = (1,2,3,4,5,6,7,8,9,10), which is circular, meaning the number after the last one is again the first one (next(10)=1).

For a given number i>0 in the range, I would like to calculate the next m-th, and previous m-th number. e.g. next(5,1)=6 next(10,1)=1 next(10,2)=2 prev(5,2)=3 prev(1,1)=10 prev(1,2)=9.

For next I can just take (i+m)%n where n is the length of the range (n=10 in the example). But for prev I couldn't find an elegant solution.


Solution 1:

Just subtract 1 and add 1 afterwards.

In most programming languages, you need to watch out when finding a "previous" value, because for negative numbers, modulo does not work as you want in this case: it returns a negative number.

Here's the C/C++ version:

int next(int i, int m, int n) { return (i + m - 1) % n + 1; }
int prev(int i, int m, int n) { return (i - m + n - 1) % n + 1; }

However, in Perl modulo always returns a positive value (at least when the second operand is a positive integer). Basically it does what you want. So you can write the following and leave out the + $_[2]:

sub nxt { ($_[0] + $_[1] - 1) % $_[2] + 1; }
sub prv { ($_[0] - $_[1] - 1) % $_[2] + 1; }

Solution 2:

Your next = (i + m) % n isn't right anyway - it'll return zero in some cases.

Try this instead:

next(i, m) = ((i - 1) + m) % n + 1
prev(i, m) = ((i - 1) + n - m) % n + 1

In effect, take one off, then find the correct value, and then add the one back on again.

For prev, add n first to ensure that you never take the modulo of a negative number

Solution 3:

What is difference between next(i,m) and previous(i,-m)? Nothing!. So let's go (i - 1 + n + m % n) % n + 1:

$ perl -le 'sub gen {my $n = shift; return sub{ my ($i, $m) = @_; return ($i - 1 + $n + $m % $n) % $n + 1;};} $"=","; for my $n (2..5) { my $f = gen($n); print "$n: @{[map {$f->(1,$_)} -10 .. 10]}"}'
2: 1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1
3: 3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,1,2
4: 3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4,1,2,3
5: 1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1,2,3,4,5,1