2010-09-27 3 views
19

Я думаю, что решение для этого довольно простое, но я об этом некоторое время думал и не мог придумать изящное решение.Как я могу по модулю, когда мои номера начинаются с 1, а не с нуля?

У меня есть ряд чисел, например. 1..10 = (1,2,3,4,5,6,7,8,9,10), который является круглым, то есть число после последнего снова является первым (next(10)=1).

Для данного номера i>0 в диапазоне, я бы хотел рассчитать следующий m-й, и предыдущий m-й номер. например next(5,1)=6next(10,1)=1next(10,2)=2prev(5,2)=3prev(1,1)=10prev(1,2)=9.

Для next я могу просто взять (i+m)%n, где n длина диапазона (n=10 в данном примере). Но для prev я не мог найти элегантное решение.

+1

Это не относится к Perl в любом случае. Я бы предложил искать лучший тег. –

+1

Тэги изменены с 'perl' на' modulo' на основе фактического содержимого вопроса. –

+0

Спасибо, ребята. –

ответ

18

Просто вычесть 1 и добавить 1 после этого.

В большинстве языков программирования вы должны следить за тем, чтобы найти «предыдущее» значение, потому что для отрицательных чисел модуль не работает так, как вы хотите в этом случае: он возвращает отрицательное число.

Здесь C/C++ версии:

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

Однако в Perl по модулю всегда возвращает положительное значение (по крайней мере, когда второй операнд является положительным целым числом). В основном он делает то, что вы хотите. Таким образом, вы можете написать следующее и оставить из + $_[2]:

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

Если число будет неотрицательным, и нет опасности численного переполнения, я предпочитаю добавлять (base-1), а не вычитать его. – supercat

+1

Хорошее обращение с различными реализациями «оператора» по модулю с математической точки зрения: http://mathforum.org/library/drmath/view/52343.html. Фактически, оператор% не определен в C/C++ для отрицательных аргументов, но большинство реализаций соответствуют стандарту IEEE 754, который аналогичен оператору REM от Ada. Perl% реализует то же, что и оператор MOD Ada. – gpvos

+1

@gpvos: Осторожно о различии между неопределенным и определенным поведением. '%' на отрицательных числах в C++ 03 является последним. –

5

Ваш next = (i + m) % n не прав в любом случае - в некоторых случаях он будет возвращать ноль.

Попробуйте вместо этого:

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

В действительности, возьмите один офф, а затем найти правильное значение, а затем добавьте один снова.

Для prev, добавьте n первым, чтобы убедиться, что вы никогда не брать по модулю отрицательного числа

+0

Это прекрасно. Спасибо. –

1

В чем разница между next(i,m) и previous(i,-m)? Ничего!. Так давайте (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 
+0

Интересно: perl modulo отличается от C по модулю. #include силы основных() { \t для (INT I = -10; г <= 10; ++ я) { \t \t Е ("% d", я% 5); \t}} дает: 0 -4 -3 -2 -1 0 -4 -3 -2 -1 0 1 2 3 4 0 1 2 3 4 0 Perl -e «для (-10 .. 10) {printf "% d", $ _% 5; } ' дает: 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 1 2 3 4 0 – gpvos

0

Несколько слов в общем-первых, если вы не возражаете.

Ваше замешательство в реализации функции «prev» происходит от мысли об этой проблеме в областях положительных и отрицательных целых чисел. Подумайте об этом с точки зрения геометрии, если вы визуализировали круг с 10 равноотстоящих точек, то решение выглядит следующим образом:

Как правильно указано, учитывая диапазон [x..z], где диапазон является круглым, вы можете найти следующий m-th number как (i+m)%k where i belongs to [x..z] и k - длина диапазона.

Теперь, для «предыдущего» m-го члена. предыдущий номер можно найти путем вычисления (или более визуально выражается, «прибывающих на») предыдущего м-й номер позиции, как это (псевдокод):

prev(m, i) = (i + len(range) - m) % len(range)

Например, если взять предыдущая первая из числа 10, а затем

prev(1,10) = (10+10-1)%10 = 19%10 = 9 

Предыдущее третье по числу 5 = prev(3,5) = (5+10-3)%10 = 12%10 = 2. Etcetera, etcetera. Очень простой и элегантный, да?

Единственная оговорка здесь в том, что if i == m, модуль будет равен нулю, поэтому для этого результата необходим механизм обработки для следующих функций() и prev().

Надеюсь, что это поможет, Jas.

1

Вы можете посмотреть источник на Tie::Cycle, модуль, который я создал для циклического перемещения по произвольным спискам.

Помните, что цифры - это всего лишь глифы, которые что-то стоят. Если у вас есть список Perl этих глифов, у вас все еще есть последовательность, начинающаяся с нуля, потому что вы делаете математику в индексах списка, а не в глифах. Когда вы выбрали индекс правого списка, вы используете элемент в этом индексе.

Если вам нужны очень большие списки или ленивые списки, вы все равно можете это сделать, но вам просто нужно немного поработать.