当我的数字从 1 开始而不是 0 时,我如何取模?
Posted
技术标签:
【中文标题】当我的数字从 1 开始而不是 0 时,我如何取模?【英文标题】:How can I modulo when my numbers start from 1, not zero? 【发布时间】:2011-04-17 17:29:56 【问题描述】:我想解决这个问题的方法很简单,但我已经考虑了一段时间,无法想出一个优雅的解决方案。
我有一系列数字,例如1..10 = (1,2,3,4,5,6,7,8,9,10)
,是循环的,表示最后一个之后的数字又是第一个(next(10)=1
)。
对于范围内的给定数字i>0
,我想计算下一个m
-th 和前一个m
-th 数字。例如next(5,1)=6
next(10,1)=1
next(10,2)=2
prev(5,2)=3
prev(1,1)=10
prev(1,2)=9
.
对于next
,我可以取(i+m)%n
,其中n
是范围的长度(示例中为n=10
)。但是对于prev
,我找不到优雅的解决方案。
【问题讨论】:
这不是 Perl 特有的。我建议寻找更好的标签。 标签从perl
更改为modulo
,基于问题的实际内容。
【参考方案1】:
只需减去 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;
【讨论】:
如果数字是非负数,并且没有数值溢出的危险,我更喜欢加 (base-1) 而不是减一。 从数学角度很好地处理模“运算符”的不同实现:mathforum.org/library/drmath/view/52343.html。实际上,C/C++ 中并没有为负参数定义 % 运算符,但大多数实现都遵循 IEEE 754 标准,这与 Ada 的 REM 运算符相同。 Perl 的 % 实现与 Ada 的 MOD 运算符相同的东西。 @gpvos:注意未定义行为和实现定义行为之间的区别。%
C++03 中的负数是后者。
不错@gpvos。我使用您的 C 示例在 javascript 的搜索结果中循环遍历 hits
的 hit
。 next
连接到 cycle(1)
和 prev
到 cycle(-1)
,其中 cycle
是 cycle (direction) this.hit = (direction === -1 ? this.hit + direction + this.hits - 1 : this.hit + direction - 1) % this.hits + 1
【参考方案2】:
您的next = (i + m) % n
无论如何都不正确 - 在某些情况下它会返回零。
试试这个:
next(i, m) = ((i - 1) + m) % n + 1
prev(i, m) = ((i - 1) + n - m) % n + 1
实际上,取下一个,然后找到正确的值,然后重新添加一个。
对于prev
,请先添加n
,以确保您永远不会取负数的模
【讨论】:
我真的很喜欢这个答案(+1)。而“取下一个,找到正确的值,然后再添加一个”的描述使一个衬垫超级直观,同时又美观简洁。【参考方案3】: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
【讨论】:
有趣:perl 取模不同于 C 取模。 #include如果你不介意的话,先说几句话。
您在实现“prev”函数时的困惑来自于在正整数和负整数域中思考这个问题。从几何的角度考虑,如果你想象一个有 10 个等距点的圆,那么解决方案看起来像这样:
正如您正确指定的那样,给定一个范围 [x..z]
,其中范围是循环的,您可以找到下一个 m-th number
作为 (i+m)%k where i belongs to [x..z]
和 k
是范围的长度。
现在,对于“以前的”第 m 个成员。 之前的数字可以通过计算(或者更直观地表达,“到达”)之前的第 m 个数字位置来找到,如下所示(伪代码):
prev(m, i) = (i + len(range) - m) % len(range)
例如,如果你取数字 10 的前一个,那么
prev(1,10) = (10+10-1)%10 = 19%10 = 9
前 3 号为 5 = prev(3,5) = (5+10-3)%10 = 12%10 = 2
。
等等等等。
非常简单,优雅,对吧?
这里唯一需要注意的是 if i == m
,模数为零,因此您需要在 next() 和 prev() 函数中处理此结果。
希望这会有所帮助, 贾斯。
【讨论】:
【参考方案5】:您可能会查看Tie::Cycle 的源代码,这是我创建的用于循环任意列表的模块。
请记住,数字实际上只是代表某些东西的字形。如果您有这些字形的 Perl 列表,您仍然有一个从零开始的序列,因为您在列表索引而不是字形上进行数学运算。选择正确的列表索引后,您将使用该索引处的元素。
如果你想要非常大的列表或惰性列表,你仍然可以这样做,但你只需要做更多的工作。
【讨论】:
【参考方案6】:我在 R 中有这个解决方案:
pred <- function(n) n - 1L # cf. Pascal's pred
succ <- function(n) n + 1L # cf. Pascal's succ
`%mod1%` <- function(m, n) succ(pred(m) %% n) # modulo from 1
cat(-11:24 %mod1% 12) # test
# 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12
【讨论】:
【参考方案7】:假设您想从 1 映射到 n 而不是 0 到 n-1 例如 n=5,范围 1 到 x,结果 0 到 4,0mod5=0 1mod5=1, 2mod5=2...xmod5 结果为 0 x=5*k。使用 ((x-1)mod5)+1,x 必须 >0。这将始终映射(计数)在 1 到 5 的范围内,而不是 0 到 4。
【讨论】:
欢迎堆栈溢出。感谢您的贡献。请让您的答案更具可读性,以便每个人都可以享受!以上是关于当我的数字从 1 开始而不是 0 时,我如何取模?的主要内容,如果未能解决你的问题,请参考以下文章
如何让 Quarkus 监听所有网络接口而不是 localhost?