我有一个list
个整数,它在一个循环中不断被修改,我需要判断它的内容是否在经过一定量的迭代后重复,以打破循环。
如果没有,list
最终将修改为[]
,或者当达到某个迭代限制时,循环终止。到目前为止我的解决方案:
def modify(numlist, a, b):
numlist = [(x * a) for x in numlist]
for i in range((len(numlist) - 1), 0, -1):
if numlist[i] >= b: numlist[i - 1] += numlist[i] // b
numlist[i] = numlist[i] % b
numlist[0] %= b
while numlist[-1] == 0:
numlist.pop(-1)
if numlist == []: break
numlist = [1, 2, 3, 4, 5]
listHistory = [numlist]
a, b = someValue, anotherValue
n = 0
while numlist != [] and n <= limit:
modify(numlist, int(a), int(b))
listHistory.append(numlist)
if numlist in listHistory: break
n += 1
limit
可能非常大(大约10**6
- 10**7
)并且检查当前numlist
对所有以前的版本变得非常慢。
是否有更有效的方法来执行此操作,甚至是一种方法来预先确定修改是否是列表初始内容的周期性,并且给定a
,b
?
答案 0 :(得分:1)
m
。它会发生什么变得乘以a然后采用模数b。它永远不会与任何其他元素混合,因此如果列表的配置必须重复,则必须遵循以下内容:
m*a^n=m modulo b
<==>a^n=1 modulo b
< >a^(n+1)=a modulo b
这是一个可以使用Fermats little theorem的问题 如果a和b是互质,那么
a^phi(b)=1 modulo b
其中phi
是Eulers totient函数。
因此,这会大大减少您必须在历史记录中存储的列表配置量。您只需每隔phi(b)
步骤存储一次。
我在这里找到了一个phi的实现: Computing Eulers Totient Function
更新:
好的,如果您要+= list[i] % b
而不是+= list[i] // b
,我找到了一个快速解决方案。否则,在最坏的情况下你需要b^4*phi(b)
个步骤
UPDATE2:
我重写了C
中的代码(见下文),使其更快,并实现了@ user2357112提出的“乌龟和野兔”算法。这样我每秒可以检查几百万个循环,这应该比python实现更快。
我尝试了一些不同的价值组合:
a b steps b^4*phi(b) (b/GCD(b,a))^4*phi(b/GCD(n,a)) (b/GCD(b,a))^4*phi(b/GCD(n,a))/steps
2 37 67469796 67469796 67469796 1
3 37 33734898 67469796 67469796 2
4 37 33734898 67469796 67469796 2
5 37 67469796 67469796 67469796 1
6 37 7496644 67469796 67469796 9
7 37 16867449 67469796 67469796 4
36 37 3748322 67469796 67469796 18
2 36 39366 20155392 629856 16
3 36 256 20155392 82944 27648
4 36 19683 20155392 39366 2
5 36 5038848 20155392 20155392 4
所以你看到它的发展方向:周期长度似乎总是(b/GCD(b,a))^4*phi(b/GCD(n,a))
的除数,所以最坏的情况是(b/GCD(b,a))^4*phi(b/GCD(n,a))
步骤被怀疑
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void modify(int *, int, int);
void printl(int * );
int main(int argc, const char*argv[])
{
int l[5]={1,2,3,4,5};
int lz[5]={1,2,3,4,5};
int i=1,a,b,n;
if (argc<4) {
printf("Not enough arguments!!\n");
exit(1);
}
a=atoi(argv[1]);
b=atoi(argv[2]);
n=atoi(argv[3]);
modify(l,a,b);
while (i<n) {
modify(l,a,b);
modify(l,a,b);
modify(lz,a,b);
i++;
if (memcmp(l,lz,sizeof(l))==0) {
printf("success!\n");
break;
}
if (i%1000000==0) printf("Step %d.000.000\n",i/1000000);
}
printf("Final step: %d\n",i);
printl(l);
printl(lz);
return 0;
}
void modify(int * li, int a, int b) {
int i=0;
while (i<=4) {
li[i]*=a;
i++;
}
i=4;
while (i>=1) {
if (li[i]>=b) {
li[i-1]+=li[i]/b;
}
li[i]=li[i]%b;
i--;
}
li[0]=li[0]%b;
}
void printl(int * li) {
printf("l=(%d,%d,%d,%d,%d)\n",li[0],li[1],li[2],li[3],li[4]);
答案 1 :(得分:0)
首先请允许我说,所有列表都是定期的,如果您考虑足够长的时间段。
那就是说,这可能是使用布隆过滤器的好地方,EG: https://pypi.python.org/pypi/drs-bloom-filter/
布隆过滤器是可以非常快速地执行集合成员资格测试的集合,并且可以在不实际存储元素数据的情况下向集合添加内容。这意味着它是概率性的,但您可以调整概率。因此,您可以使用布隆过滤器测试进行快速检查,并在使用布隆过滤器检测到匹配时,使用慢速+确定性算法确认结果。
用法如下:
In [1]: import bloom_filter_mod
In [2]: bloom_filter = bloom_filter_mod.Bloom_filter(1000000, 0.01)
In [3]: for i in range(10):
...: bloom_filter.add(i)
...:
In [4]: for i in range(0, 20, 2):
...: if i in bloom_filter:
...: print('{} present'.format(i))
...:
0 present
2 present
4 present
6 present
8 present
1000000是您要在过滤器中存储的元素的最大数量,0.01是满月时误报的最大概率。
因此,您可以在过滤器中“存储”每个子序列,并快速检测重复发生。
答案 2 :(得分:0)
您的list
(顺便说一下,您真正应该重命名)会在基座b**something
中存储一个数字b
。每次运行modify
会将数字乘以a
,然后在表示结尾处截断零。
拨打列表n
最初代表的号码,并调用列表l
的原始长度。如果此过程终止,它将在第一次迭代k
执行此操作,以便b**l
除n * a**k
,这将仅在b**l / gcd(n, b**l)
的所有素因子时出现是a
的因素。这很容易确定:
def all_prime_factors_of_first_divide_second(a, b):
while a != 1:
factor = gcd(a, b)
if factor == 1:
return False
while not a % factor:
a //= factor
return True